From 90f7e9f75d5cd8c417ddb03bb798043c2c7e8a4d Mon Sep 17 00:00:00 2001 From: MedovTimur Date: Mon, 5 Feb 2024 14:53:52 +0300 Subject: [PATCH 1/8] update --- io/src/lib.rs | 11 +++++------ music-nft/io/src/lib.rs | 2 ++ music-nft/src/lib.rs | 15 ++++++++++----- nft/io/src/lib.rs | 2 ++ nft/src/lib.rs | 29 ++++++++++++++++++++--------- src/lib.rs | 31 ++++++++++++++++--------------- src/sale.rs | 2 +- tests/test_marketplace.rs | 16 ++++++++-------- tests/test_nft.rs | 2 -- 9 files changed, 64 insertions(+), 46 deletions(-) diff --git a/io/src/lib.rs b/io/src/lib.rs index 1793a4f..601638c 100644 --- a/io/src/lib.rs +++ b/io/src/lib.rs @@ -16,11 +16,11 @@ impl Metadata for NftMarketplaceMetadata { /// * gas_for_transfer_token - gas that is needed to transfer nft tokens (in case of sale, auction or offer) /// * gas_for_close_auction - gas which is needed to send a delayed message to close the auction /// (this action includes a transfer, so gas_for_close_auction must be greater than gas_for_transfer_token) -/// * gas_for_delete_collection - gas that is needed to delete a collection +/// * gas_for_delete_collection - gas that is needed to delete a collection /// (a message is sent to the collection contract to see if the collection can be deleted) /// * gas_for_get_token_info - gas which is needed to get information from the collection about the token /// (used for sale, auction and offers) -/// * time_between_create_collections - time between collection creation +/// * time_between_create_collections - time between collection creation /// (to avoid regular users from creating collections too often) /// * minimum_transfer_value - minimum allowable transfer value /// * ms_in_block - number of milliseconds in one block @@ -28,10 +28,10 @@ impl Metadata for NftMarketplaceMetadata { #[derive(Encode, Decode, TypeInfo)] pub struct NftMarketplaceInit { pub gas_for_creation: u64, - pub gas_for_transfer_token: u64, + pub gas_for_transfer_token: u64, pub gas_for_close_auction: u64, pub gas_for_delete_collection: u64, - pub gas_for_get_token_info: u64, + pub gas_for_get_token_info: u64, pub time_between_create_collections: u64, pub minimum_transfer_value: u128, pub ms_in_block: u32, @@ -229,7 +229,7 @@ pub struct State { pub config: Config, } -/// * code_id - code_id is used to create a collection by that CodeId, +/// * code_id - code_id is used to create a collection by that CodeId, /// the admin should preload the NFT contract and specify in the marketplace so that regular users can use it /// * meta_link - it is necessary to set a reference where the meta of this collection type will be stored for further interaction with the contract /// * type_description - description of this type of collection @@ -247,7 +247,6 @@ pub struct CollectionInfo { pub meta_link: String, } - #[derive(Default, Debug, Encode, Decode, TypeInfo, Clone)] pub struct Config { pub gas_for_creation: u64, diff --git a/music-nft/io/src/lib.rs b/music-nft/io/src/lib.rs index 37061ac..3e92c57 100644 --- a/music-nft/io/src/lib.rs +++ b/music-nft/io/src/lib.rs @@ -128,6 +128,7 @@ pub enum MusicNftEvent { CanDelete(bool), Initialized { config: Config, + total_number_of_tokens: Option, }, Minted { token_id: NftId, @@ -142,6 +143,7 @@ pub enum MusicNftEvent { }, Expanded { additional_links: Vec<(Links, ImageData)>, + total_number_of_tokens: Option, }, ConfigChanged { config: Config, diff --git a/music-nft/src/lib.rs b/music-nft/src/lib.rs index 78f6476..3d94276 100644 --- a/music-nft/src/lib.rs +++ b/music-nft/src/lib.rs @@ -231,7 +231,10 @@ impl NftContract { } self.total_number_of_tokens = number_tokens; - Ok(MusicNftEvent::Expanded { additional_links }) + Ok(MusicNftEvent::Expanded { + additional_links, + total_number_of_tokens: number_tokens, + }) } fn change_config(&mut self, config: Config) -> Result { @@ -492,8 +495,9 @@ extern "C" fn init() { msg::send( collection_owner, - Ok::(MusicNftEvent::Initialized { + Ok::(MusicNftEvent::Initialized { config: config.clone(), + total_number_of_tokens, }), 0, ) @@ -502,6 +506,7 @@ extern "C" fn init() { msg::reply( MusicNftEvent::Initialized { config: config.clone(), + total_number_of_tokens, }, 0, ) @@ -604,9 +609,9 @@ impl From for NftState { } fn sum_limit_copies(links_and_data: &[(Links, ImageData)]) -> Option { - let sum = links_and_data - .iter() - .try_fold(0, |acc, (_, img_data)| img_data.limit_copies.map(|y| acc + y as u64)); + let sum = links_and_data.iter().try_fold(0, |acc, (_, img_data)| { + img_data.limit_copies.map(|y| acc + y as u64) + }); sum } diff --git a/nft/io/src/lib.rs b/nft/io/src/lib.rs index 352af02..203370f 100644 --- a/nft/io/src/lib.rs +++ b/nft/io/src/lib.rs @@ -119,6 +119,7 @@ pub enum NftEvent { CanDelete(bool), Initialized { config: Config, + total_number_of_tokens: Option, }, Minted { token_id: NftId, @@ -133,6 +134,7 @@ pub enum NftEvent { }, Expanded { additional_links: Vec<(String, ImageData)>, + total_number_of_tokens: Option, }, ConfigChanged { config: Config, diff --git a/nft/src/lib.rs b/nft/src/lib.rs index ea0f19c..a4c6827 100644 --- a/nft/src/lib.rs +++ b/nft/src/lib.rs @@ -208,7 +208,10 @@ impl NftContract { } self.total_number_of_tokens = number_tokens; - Ok(NftEvent::Expanded { additional_links }) + Ok(NftEvent::Expanded { + additional_links, + total_number_of_tokens: number_tokens, + }) } fn change_config(&mut self, config: Config) -> Result { @@ -354,7 +357,9 @@ impl NftContract { )); } } else { - return Err(NftError("Caller is not approved to perform transfer.".to_owned())); + return Err(NftError( + "Caller is not approved to perform transfer.".to_owned(), + )); } Ok(()) } @@ -456,19 +461,26 @@ extern "C" fn init() { nonce: 0, img_links_and_data, collection_owner, - total_number_of_tokens + total_number_of_tokens, }) }; msg::send( collection_owner, Ok::(NftEvent::Initialized { config: config.clone(), + total_number_of_tokens, }), 0, ) .expect("Error during send to owner `NftEvent::Initialized`"); - msg::reply(NftEvent::Initialized { config }, 0) - .expect("Error during send reply `NftEvent::Initialized`"); + msg::reply( + NftEvent::Initialized { + config, + total_number_of_tokens, + }, + 0, + ) + .expect("Error during send reply `NftEvent::Initialized`"); } #[no_mangle] @@ -563,13 +575,12 @@ impl From for NftState { total_number_of_tokens, } } - } fn sum_limit_copies(img_links_and_data: &[(String, ImageData)]) -> Option { - let sum = img_links_and_data - .iter() - .try_fold(0, |acc, (_, img_data)| img_data.limit_copies.map(|y| acc + y as u64)); + let sum = img_links_and_data.iter().try_fold(0, |acc, (_, img_data)| { + img_data.limit_copies.map(|y| acc + y as u64) + }); sum } diff --git a/src/lib.rs b/src/lib.rs index d1a0338..c9c4872 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -222,7 +222,8 @@ impl NftMarketplace { .await .expect("Program was not initialized"); - self.collection_to_owner.insert(address, (type_name.clone(), msg_src)); + self.collection_to_owner + .insert(address, (type_name.clone(), msg_src)); self.time_creation .entry(msg_src) @@ -378,7 +379,10 @@ impl NftMarketplace { } } - fn get_collection_info(&self, type_name: &str) -> Result<&TypeCollectionInfo, NftMarketplaceError> { + fn get_collection_info( + &self, + type_name: &str, + ) -> Result<&TypeCollectionInfo, NftMarketplaceError> { if let Some(collection_info) = self.type_collections.get(type_name) { Ok(collection_info) } else { @@ -398,12 +402,8 @@ extern "C" fn state() { }; let query: StateQuery = msg::load().expect("Unable to load the state query"); let reply = match query { - StateQuery::All => { - StateReply::All(nft_marketplace.into()) - } - StateQuery::Admins => { - StateReply::Admins(nft_marketplace.admins) - } + StateQuery::All => StateReply::All(nft_marketplace.into()), + StateQuery::Admins => StateReply::Admins(nft_marketplace.admins), StateQuery::CollectionsInfo => { let type_collections = nft_marketplace .type_collections @@ -412,9 +412,7 @@ extern "C" fn state() { .collect(); StateReply::CollectionsInfo(type_collections) } - StateQuery::Config => { - StateReply::Config(nft_marketplace.config) - } + StateQuery::Config => StateReply::Config(nft_marketplace.config), StateQuery::AllCollections => { let collection_to_owner = nft_marketplace .collection_to_owner @@ -426,8 +424,12 @@ extern "C" fn state() { StateQuery::GetCollectionInfo(collection_address) => { let collection_to_owner = nft_marketplace.collection_to_owner.get(&collection_address); if let Some((type_name, owner)) = collection_to_owner { - let meta_link = &nft_marketplace.type_collections.get(type_name).expect("This collection type name must exist").meta_link; - let collection_info = CollectionInfo{ + let meta_link = &nft_marketplace + .type_collections + .get(type_name) + .expect("This collection type name must exist") + .meta_link; + let collection_info = CollectionInfo { owner: *owner, type_name: type_name.clone(), meta_link: meta_link.clone(), @@ -436,8 +438,7 @@ extern "C" fn state() { } else { StateReply::CollectionInfo(None) } - - } + } }; msg::reply(reply, 0).expect("Unable to share the state"); } diff --git a/src/sale.rs b/src/sale.rs index b1216df..d47ecc0 100644 --- a/src/sale.rs +++ b/src/sale.rs @@ -50,7 +50,7 @@ impl NftMarketplace { ) .await?; - // send a message to the nft contract to transfer the token to the marketplace address + // send a message to the nft contract to transfer the token to the marketplace address // so that the token can be immediately transferred to the buyer upon purchase. if let NftEvent::Transferred { owner, diff --git a/tests/test_marketplace.rs b/tests/test_marketplace.rs index ccdf237..bc188fb 100644 --- a/tests/test_marketplace.rs +++ b/tests/test_marketplace.rs @@ -105,7 +105,7 @@ fn create_success() { .expect("Unexpected invalid state."); if let StateReply::AllCollections(state) = state_reply { assert!(!state.is_empty(), "Collections shouldn't be empty"); - assert_eq!(state[0].1.1, USERS[0].into(), "Wrong owner of collection"); + assert_eq!(state[0].1 .1, USERS[0].into(), "Wrong owner of collection"); } sys.spend_blocks(7200); let res = create_collection( @@ -317,7 +317,7 @@ fn sale_success() { .expect("Unexpected invalid state."); let address_nft = if let StateReply::AllCollections(state) = state_reply { assert!(!state.is_empty(), "Collections shouldn't be empty"); - assert_eq!(state[0].1.1, USERS[0].into(), "Wrong owner of collection"); + assert_eq!(state[0].1 .1, USERS[0].into(), "Wrong owner of collection"); state[0].0 } else { assert!(false, "Unexpected StateReply variant"); @@ -426,7 +426,7 @@ fn sale_failures() { .expect("Unexpected invalid state."); let address_nft = if let StateReply::AllCollections(state) = state_reply { assert!(!state.is_empty(), "Collections shouldn't be empty"); - assert_eq!(state[0].1.1, USERS[0].into(), "Wrong owner of collection"); + assert_eq!(state[0].1 .1, USERS[0].into(), "Wrong owner of collection"); state[0].0 } else { assert!(false, "Unexpected StateReply variant"); @@ -606,7 +606,7 @@ fn auction_success() { .expect("Unexpected invalid state."); let address_nft = if let StateReply::AllCollections(state) = state_reply { assert!(!state.is_empty(), "Collections shouldn't be empty"); - assert_eq!(state[0].1.1, USERS[0].into(), "Wrong owner of collection"); + assert_eq!(state[0].1 .1, USERS[0].into(), "Wrong owner of collection"); state[0].0 } else { assert!(false, "Unexpected StateReply variant"); @@ -762,7 +762,7 @@ fn auction_cancel() { .expect("Unexpected invalid state."); let address_nft = if let StateReply::AllCollections(state) = state_reply { assert!(!state.is_empty(), "Collections shouldn't be empty"); - assert_eq!(state[0].1.1, USERS[0].into(), "Wrong owner of collection"); + assert_eq!(state[0].1 .1, USERS[0].into(), "Wrong owner of collection"); state[0].0 } else { assert!(false, "Unexpected StateReply variant"); @@ -944,7 +944,7 @@ fn auction_failures() { .expect("Unexpected invalid state."); let address_nft = if let StateReply::AllCollections(state) = state_reply { assert!(!state.is_empty(), "Collections shouldn't be empty"); - assert_eq!(state[0].1.1, USERS[0].into(), "Wrong owner of collection"); + assert_eq!(state[0].1 .1, USERS[0].into(), "Wrong owner of collection"); state[0].0 } else { assert!(false, "Unexpected StateReply variant"); @@ -1121,7 +1121,7 @@ fn offer_success() { .expect("Unexpected invalid state."); let address_nft = if let StateReply::AllCollections(state) = state_reply { assert!(!state.is_empty(), "Collections shouldn't be empty"); - assert_eq!(state[0].1.1, USERS[0].into(), "Wrong owner of collection"); + assert_eq!(state[0].1 .1, USERS[0].into(), "Wrong owner of collection"); state[0].0 } else { assert!(false, "Unexpected StateReply variant"); @@ -1245,7 +1245,7 @@ fn offer_failures() { .expect("Unexpected invalid state."); let address_nft = if let StateReply::AllCollections(state) = state_reply { assert!(!state.is_empty(), "Collections shouldn't be empty"); - assert_eq!(state[0].1.1, USERS[0].into(), "Wrong owner of collection"); + assert_eq!(state[0].1 .1, USERS[0].into(), "Wrong owner of collection"); state[0].0 } else { assert!(false, "Unexpected StateReply variant"); diff --git a/tests/test_nft.rs b/tests/test_nft.rs index 9106733..795da39 100644 --- a/tests/test_nft.rs +++ b/tests/test_nft.rs @@ -747,8 +747,6 @@ fn get_state(nft_collection: &Program) -> Option { // } - - // #[test] // fn check() { // let input = "00033c5573657220436f6c6c656374696f6e3c5573657220436f6c6c656374696f6e04107461673144436f6c6c656374696f6e2062616e6e65723c436f6c6c656374696f6e206c6f676f010300000000000000000000000000000000000000000000010000000000000000010000000000000000"; From 620bca49ded02048435d89dc8298baacdad116a4 Mon Sep 17 00:00:00 2001 From: MedovTimur Date: Sat, 17 Feb 2024 20:42:16 +0300 Subject: [PATCH 2/8] update marketplace and nft --- io/src/lib.rs | 41 +- nft/io/src/lib.rs | 37 +- nft/src/lib.rs | 371 +++--- src/auction.rs | 168 ++- src/lib.rs | 38 +- src/nft_messages.rs | 23 +- src/offer.rs | 32 +- src/sale.rs | 44 +- tests/marketplace_node_test.rs | 4 + tests/test_marketplace.rs | 190 +--- tests/test_music_nft.rs | 1932 ++++++++++++++++---------------- tests/test_nft.rs | 160 ++- tests/utils/mod.rs | 18 +- 13 files changed, 1550 insertions(+), 1508 deletions(-) diff --git a/io/src/lib.rs b/io/src/lib.rs index 601638c..45eac6c 100644 --- a/io/src/lib.rs +++ b/io/src/lib.rs @@ -195,7 +195,26 @@ pub enum NftMarketplaceEvent { } #[derive(Debug, Clone, Encode, Decode, TypeInfo)] -pub struct NftMarketplaceError(pub String); +pub enum NftMarketplaceError { + WrongCollectionAddress, + AlreadyOnAuction, + AuctionClosed, + AlreadyOnSale, + LessThanExistentialDeposit, + WrongReply, + WrongCollectionName, + AccessDenied, + DeadlineError, + ThereIsNoSuchAuction, + LessOrEqualThanBid, + NotSellable, + NoApproveToMarketplace, + ErrorFromCollection, + WrongDataOffer, + OfferDoesNotExist, + SaleDoesNotExist, + ValueIsLessThanPrice, +} #[derive(Encode, Decode, TypeInfo)] pub enum StateQuery { @@ -318,4 +337,22 @@ pub enum NftEvent { } #[derive(Debug, Clone, Encode, Decode, TypeInfo)] -pub struct NftError(pub String); +pub enum NftError { + MathOverflow, + ErrorGettingRandom, + TokenDoesNotExist, + AllTokensMinted, + OwnerDoesNotHaveNft, + AccessDenied, + NoApproval, + ThereIsApproval, + LimitIsZero, + ConfigCannotBeChanged, + WrongRoyalty, + NotTransferable, + UserRestrictionCannotBeChanged, + NoListOfRestriction, + ThereIsNoSuchUser, + ExhaustedLimit, + WrongValue, +} diff --git a/nft/io/src/lib.rs b/nft/io/src/lib.rs index 203370f..0784939 100644 --- a/nft/io/src/lib.rs +++ b/nft/io/src/lib.rs @@ -25,6 +25,7 @@ pub struct NftInit { pub collection_owner: ActorId, pub config: Config, pub img_links_and_data: Vec<(String, ImageData)>, + pub permission_to_mint: Option>, } #[derive(Debug, Clone, Encode, Decode, TypeInfo)] @@ -100,6 +101,13 @@ pub enum NftAction { token_id: NftId, metadata: String, }, + AddUsersForMint { + users: Vec, + }, + DeleteUserForMint { + user: ActorId, + }, + LiftRestrictionMint, } #[derive(Debug, Clone, Encode, Decode, TypeInfo)] @@ -120,6 +128,7 @@ pub enum NftEvent { Initialized { config: Config, total_number_of_tokens: Option, + permission_to_mint: Option>, }, Minted { token_id: NftId, @@ -147,9 +156,34 @@ pub enum NftEvent { token_id: NftId, metadata: String, }, + UsersForMintAdded { + users: Vec, + }, + UserForMintDeleted { + user: ActorId, + }, + LiftRestrictionMint, } #[derive(Debug, Clone, Encode, Decode, TypeInfo)] -pub struct NftError(pub String); +pub enum NftError { + MathOverflow, + ErrorGettingRandom, + TokenDoesNotExist, + AllTokensMinted, + OwnerDoesNotHaveNft, + AccessDenied, + NoApproval, + ThereIsApproval, + LimitIsZero, + ConfigCannotBeChanged, + WrongRoyalty, + NotTransferable, + UserRestrictionCannotBeChanged, + NoListOfRestriction, + ThereIsNoSuchUser, + ExhaustedLimit, + WrongValue, +} #[derive(Debug, Clone, Encode, Decode, TypeInfo)] pub struct NftState { @@ -161,6 +195,7 @@ pub struct NftState { pub img_links_and_data: Vec<(String, ImageData)>, pub collection_owner: ActorId, pub total_number_of_tokens: Option, + pub permission_to_mint: Option>, } #[derive(Debug, Clone, Encode, Decode, TypeInfo)] diff --git a/nft/src/lib.rs b/nft/src/lib.rs index a4c6827..b6650bd 100644 --- a/nft/src/lib.rs +++ b/nft/src/lib.rs @@ -1,7 +1,7 @@ #![no_std] use gstd::{ collections::{HashMap, HashSet}, - debug, exec, msg, + exec, msg, prelude::*, ActorId, }; @@ -20,60 +20,67 @@ struct NftContract { pub nonce: NftId, pub img_links_and_data: Vec<(String, ImageData)>, pub collection_owner: ActorId, + pub permission_to_mint: Option>, pub total_number_of_tokens: Option, } static mut NFT_CONTRACT: Option = None; impl NftContract { - fn mint(&mut self) -> Result { - let msg_src = msg::source(); + fn mint(&mut self, msg_src: ActorId, msg_value: u128) -> Result { + // check if there are tokens for mint self.check_available_amount_of_tokens()?; - self.check_mint_limit(&msg_src)?; + + // check if a user can make a mint: + // - quantity limit + // - user-specific limit + self.check_mint(&msg_src)?; + let Some(next_nft_nonce) = self.nonce.checked_add(1) else { - return Err(NftError("Math overflow.".to_owned())); + return Err(NftError::MathOverflow); }; - self.payment_for_mint()?; + // value check on mint + self.payment_for_mint(msg_value)?; let rand_index = get_random_value(self.img_links_and_data.len() as u64); - let img_link: String; let token_id = self.nonce; - if let Some((link, img_info)) = self.img_links_and_data.get_mut(rand_index as usize) { - if let Some(limit) = img_info.limit_copies.as_mut() { - *limit -= 1; - } - img_link = link.clone(); - - if let Some(auto_changing_rules) = &img_info.auto_changing_rules { - auto_changing_rules - .iter() - .for_each(|(trigger_time, auto_action)| { - let action = match &auto_action { - Action::ChangeImg(img_link) => NftAction::ChangeImg { - token_id, - img_link: img_link.to_string(), - }, - Action::AddMeta(metadata) => NftAction::AddMetadata { - token_id, - metadata: metadata.to_string(), - }, - }; - msg::send_with_gas_delayed( - exec::program_id(), - action, - GAS_AUTO_CHANGING, - 0, - *trigger_time / BLOCK_DURATION_IN_SECS, - ) - .expect("Error in sending a delayed message `NftAction`"); - }); - } + let (link, img_info) = self + .img_links_and_data + .get_mut(rand_index as usize) + .ok_or(NftError::ErrorGettingRandom)?; + + img_info.limit_copies.as_mut().map(|limit| *limit -= 1); + + let img_link = link.clone(); + + if let Some(auto_changing_rules) = &img_info.auto_changing_rules { + auto_changing_rules + .iter() + .for_each(|(trigger_time, auto_action)| { + let action = match &auto_action { + Action::ChangeImg(img_link) => NftAction::ChangeImg { + token_id, + img_link: img_link.to_string(), + }, + Action::AddMeta(metadata) => NftAction::AddMetadata { + token_id, + metadata: metadata.to_string(), + }, + }; + msg::send_with_gas_delayed( + exec::program_id(), + action, + GAS_AUTO_CHANGING, + 0, + *trigger_time / BLOCK_DURATION_IN_SECS, + ) + .expect("Error in sending a delayed message `NftAction`"); + }); + } - if let Some(0) = img_info.limit_copies { - self.img_links_and_data.remove(rand_index as usize); - } - } else { - return Err(NftError("Error with getting a random nft".to_owned())); + // if there are 0 copies of this token left, then delete this token + if let Some(0) = img_info.limit_copies { + self.img_links_and_data.remove(rand_index as usize); } self.owners @@ -109,20 +116,18 @@ impl NftContract { to: &ActorId, token_id: NftId, ) -> Result { + // checking for the possibility to transfer self.can_transfer(from, to, &token_id)?; - let nft = self - .tokens - .get_mut(&token_id) - .expect("NonFungibleToken: token does not exist"); + let nft = self.tokens.get_mut(&token_id).ok_or(NftError::TokenDoesNotExist)?; + let tokens = self + .owners + .get_mut(from) + .ok_or(NftError::OwnerDoesNotHaveNft)?; + tokens.retain(|&token| token != token_id); - if let Some(tokens) = self.owners.get_mut(from) { - tokens.retain(|&token| token != token_id); - if tokens.is_empty() { - self.owners.remove(from); - } - } else { - return Err(NftError("Fatal: owner does not contain nft id.".to_owned())); + if tokens.is_empty() { + self.owners.remove(from); } nft.owner = *to; @@ -143,6 +148,7 @@ impl NftContract { }) } fn approve(&mut self, to: &ActorId, token_id: NftId) -> Result { + // checking for the possibility to give an approve self.can_approve(&token_id)?; self.token_approvals.insert(token_id, *to); @@ -150,41 +156,30 @@ impl NftContract { } fn revoke_approve(&mut self, token_id: NftId) -> Result { - if let Some(nft_info) = self.tokens.get(&token_id) { - if nft_info.owner != msg::source() { - return Err(NftError("Only nft owner can send this message".to_owned())); - } - } else { - return Err(NftError("This nft id hasn't come out yet".to_owned())); - } - - let res = self.token_approvals.remove(&token_id); - if res.is_none() { - return Err(NftError( - "No approve has been issued to this token".to_owned(), - )); + let nft_info = self + .tokens + .get(&token_id) + .ok_or(NftError::TokenDoesNotExist)?; + if nft_info.owner != msg::source() { + return Err(NftError::AccessDenied); } + let _ = self.token_approvals.remove(&token_id).ok_or(NftError::NoApproval); Ok(NftEvent::ApprovalRevoked { token_id }) } fn get_token_info(&self, token_id: NftId) -> Result { - let nft = self.tokens.get(&token_id); - let (token_owner, can_sell) = if let Some(nft) = nft { - let can_sell = if let Some(time) = self.config.sellable { - exec::block_timestamp() >= nft.mint_time + time - } else { - false - }; - (nft.owner, can_sell) - } else { - return Err(NftError( - "NonFungibleToken: token does not exist".to_owned(), - )); - }; + let nft = self.tokens.get(&token_id).ok_or(NftError::TokenDoesNotExist)?; + + let can_sell = self + .config + .sellable + .map(|time| exec::block_timestamp() >= nft.mint_time + time) + .unwrap_or_default(); + let approval = self.token_approvals.get(&token_id).copied(); Ok(NftEvent::TokenInfoReceived { - token_owner, + token_owner: nft.owner, approval, sellable: can_sell, collection_owner: self.collection_owner, @@ -192,20 +187,21 @@ impl NftContract { }) } fn expand(&mut self, additional_links: Vec<(String, ImageData)>) -> Result { - self.check_collection_owner()?; + let msg_src = msg::source(); + self.check_collection_owner(msg_src)?; if additional_links .iter() .any(|(_, img_data)| img_data.limit_copies.map_or(false, |limit| limit == 0)) { - return Err(NftError("Limit of copies value is equal to 0".to_owned())); + return Err(NftError::LimitIsZero); } self.img_links_and_data.extend(additional_links.clone()); - let mut number_tokens = sum_limit_copies(&self.img_links_and_data); - if let Some(all_copies) = number_tokens.as_mut() { - *all_copies += self.tokens.len() as u64; - } + // The tokens that have already been minted should be added as well + number_tokens + .as_mut() + .map(|all_copies| *all_copies += self.tokens.len() as u64); self.total_number_of_tokens = number_tokens; Ok(NftEvent::Expanded { @@ -215,29 +211,24 @@ impl NftContract { } fn change_config(&mut self, config: Config) -> Result { - self.check_collection_owner()?; + let msg_src = msg::source(); + self.check_collection_owner(msg_src)?; if !self.tokens.is_empty() { - return Err(NftError( - "The collection configuration can no more be changed".to_owned(), - )); + return Err(NftError::ConfigCannotBeChanged); } // made 10_000 so you can enter hundredths of a percent. if config.royalty > 10_000 { - return Err(NftError( - "Royalty percent must be less than 100%".to_owned(), - )); + return Err(NftError::WrongRoyalty); } if config.transferable.is_none() && config.sellable.is_some() { - return Err(NftError("Tokens must be transferable".to_owned())); + return Err(NftError::NotTransferable); } if let Some(limit) = config.user_mint_limit { if limit == 0 { - return Err(NftError( - "The mint limit must be greater than zero".to_owned(), - )); + return Err(NftError::LimitIsZero); } } self.config = config.clone(); @@ -246,13 +237,10 @@ impl NftContract { fn change_image(&mut self, token_id: NftId, img_link: String) -> Result { if exec::program_id() != msg::source() { - return Err(NftError("Only program can send this message".to_owned())); + return Err(NftError::AccessDenied); } - let nft = self - .tokens - .get_mut(&token_id) - .expect("Token does not exist"); + let nft = self.tokens.get_mut(&token_id).ok_or(NftError::TokenDoesNotExist)?; nft.media_url = img_link.clone(); msg::send( @@ -269,14 +257,11 @@ impl NftContract { fn add_metadata(&mut self, token_id: NftId, metadata: String) -> Result { if exec::program_id() != msg::source() { - return Err(NftError("Only program can send this message".to_owned())); + return Err(NftError::AccessDenied); } - let nft = self - .tokens - .get_mut(&token_id) - .expect("Token does not exist"); - + let nft = self.tokens.get_mut(&token_id).ok_or(NftError::TokenDoesNotExist)?; nft.metadata.push(metadata.clone()); + msg::send( nft.owner, NftEvent::MetadataAdded { @@ -289,41 +274,93 @@ impl NftContract { Ok(NftEvent::MetadataAdded { token_id, metadata }) } + fn add_users_for_mint(&mut self, users: Vec) -> Result { + let msg_src = msg::source(); + self.check_collection_owner(msg_src)?; + if let Some(allowed_users) = &mut self.permission_to_mint { + allowed_users.extend(users.clone()); + allowed_users.sort(); + allowed_users.dedup(); + } else { + if self.tokens.is_empty() { + let mut new_users = users.clone(); + new_users.push(msg_src); + self.permission_to_mint = Some(new_users); + } else { + return Err(NftError::UserRestrictionCannotBeChanged); + } + } + + Ok(NftEvent::UsersForMintAdded { users }) + } + fn delete_user_for_mint(&mut self, user: ActorId) -> Result { + let msg_src = msg::source(); + self.check_collection_owner(msg_src)?; + let allowed_users = self.permission_to_mint.as_mut().ok_or(NftError::NoListOfRestriction)?; + if let Some(pos) = allowed_users + .iter() + .position(|allowed_user| *allowed_user == user) + { + allowed_users.remove(pos); + } else { + return Err(NftError::ThereIsNoSuchUser); + } + + Ok(NftEvent::UserForMintDeleted { user }) + } + + fn lift_restrictions_mint(&mut self) -> Result { + let msg_src = msg::source(); + self.check_collection_owner(msg_src)?; + + if !self.tokens.is_empty() { + return Err(NftError::UserRestrictionCannotBeChanged); + } + self.permission_to_mint = None; + + Ok(NftEvent::LiftRestrictionMint) + } + fn can_delete(&self) -> Result { Ok(NftEvent::CanDelete(self.tokens.is_empty())) } - fn check_collection_owner(&self) -> Result<(), NftError> { - if self.collection_owner != msg::source() { - return Err(NftError( - "Only collection owner can send this message".to_owned(), - )); + fn check_collection_owner(&self, msg_src: ActorId) -> Result<(), NftError> { + if self.collection_owner != msg_src { + return Err(NftError::AccessDenied); } Ok(()) } fn check_available_amount_of_tokens(&self) -> Result<(), NftError> { if self.img_links_and_data.is_empty() { - return Err(NftError("All tokens are minted.".to_owned())); + return Err(NftError::AllTokensMinted); } Ok(()) } - fn check_mint_limit(&self, user: &ActorId) -> Result<(), NftError> { + fn check_mint(&self, user: &ActorId) -> Result<(), NftError> { + if let Some(permission_to_mint) = &self.permission_to_mint { + if !permission_to_mint.contains(user) { + return Err(NftError::AccessDenied); + } + } + if let Some(limit) = self.config.user_mint_limit { if let Some(number_tokens) = self.restriction_mint.get(user) { if number_tokens >= &limit && self.collection_owner != *user { - return Err(NftError("You've exhausted your limit.".to_owned())); + return Err(NftError::ExhaustedLimit); } } } + Ok(()) } // Checking for sufficient mint value and sending the value to the creator - fn payment_for_mint(&self) -> Result<(), NftError> { + fn payment_for_mint(&self, msg_value: u128) -> Result<(), NftError> { if self.config.payment_for_mint != 0 { - if msg::value() != self.config.payment_for_mint { - return Err(NftError("Incorrectly entered mint fee.".to_owned())); + if msg_value != self.config.payment_for_mint { + return Err(NftError::WrongValue); } // use send_with_gas to transfer the value directly to the balance, not to the mailbox. msg::send_with_gas(self.collection_owner, "", 0, self.config.payment_for_mint) @@ -335,67 +372,48 @@ impl NftContract { // verification of approval fn can_approve(&self, token_id: &NftId) -> Result<(), NftError> { if self.token_approvals.contains_key(token_id) { - return Err(NftError("Approve has already been issued".to_owned())); + return Err(NftError::ThereIsApproval); } - if let Some(nft_info) = self.tokens.get(token_id) { - if nft_info.owner != msg::source() { - return Err(NftError("Only nft owner can send this message".to_owned())); - } - } else { - return Err(NftError("This nft id hasn't come out yet".to_owned())); + let nft_info = self + .tokens + .get(token_id) + .ok_or(NftError::TokenDoesNotExist)?; + if nft_info.owner != msg::source() { + return Err(NftError::AccessDenied); } - Ok(()) } // approval check fn check_approve(&self, user: &ActorId, token_id: &NftId) -> Result<(), NftError> { - if let Some(approved_account) = self.token_approvals.get(token_id) { - if approved_account != user { - return Err(NftError( - "Caller is not approved to perform transfer.".to_owned(), - )); - } - } else { - return Err(NftError( - "Caller is not approved to perform transfer.".to_owned(), - )); + let approved_account = self.token_approvals.get(token_id).ok_or(NftError::AccessDenied)?; + if approved_account != user { + return Err(NftError::AccessDenied); } + Ok(()) } // doing all the checks to verify that the transfer can be made fn can_transfer(&self, from: &ActorId, to: &ActorId, token_id: &NftId) -> Result<(), NftError> { - if let Some(nft) = self.tokens.get(token_id) { - let owner = nft.owner; - if owner != *from { - return Err(NftError("NonFungibleToken: access denied".to_owned())); - } - let msg_src = msg::source(); - // if the owner of the token does not match the sender of the message, then check the approval - if owner != msg_src { - self.check_approve(&msg_src, token_id)?; - } - if let Some(time) = self.config.transferable { - if exec::block_timestamp() < nft.mint_time + time { - return Err(NftError( - "NonFungibleToken: transfer will be available after the deadline" - .to_owned(), - )); - } - } else { - return Err(NftError( - "NonFungibleToken: token is not transferable".to_owned(), - )); - } - } else { - return Err(NftError( - "NonFungibleToken: token does not exist".to_owned(), - )); + let nft = self.tokens.get(token_id).ok_or(NftError::TokenDoesNotExist)?; + + let owner = nft.owner; + if owner != *from { + return Err(NftError::AccessDenied); + } + let msg_src = msg::source(); + // if the owner of the token does not match the sender of the message, then check the approval + if owner != msg_src { + self.check_approve(&msg_src, token_id)?; + } + let time = self.config.transferable.ok_or(NftError::NotTransferable)?; + if exec::block_timestamp() < nft.mint_time + time { + return Err(NftError::NotTransferable); } if from == to { - return Err(NftError("Self transfer is not allowed.".to_owned())); + return Err(NftError::AccessDenied); } Ok(()) } @@ -407,8 +425,8 @@ extern "C" fn init() { collection_owner, config, img_links_and_data, + mut permission_to_mint, } = msg::load().expect("Unable to decode `NftInit`."); - debug!("INIT NFT"); assert!( config @@ -450,6 +468,11 @@ extern "C" fn init() { } let total_number_of_tokens = sum_limit_copies(&img_links_and_data); + if let Some(users) = permission_to_mint.as_mut() { + if !users.contains(&collection_owner){ + users.push(collection_owner); + } + } unsafe { NFT_CONTRACT = Some(NftContract { @@ -462,6 +485,7 @@ extern "C" fn init() { img_links_and_data, collection_owner, total_number_of_tokens, + permission_to_mint: permission_to_mint.clone(), }) }; msg::send( @@ -469,6 +493,7 @@ extern "C" fn init() { Ok::(NftEvent::Initialized { config: config.clone(), total_number_of_tokens, + permission_to_mint: permission_to_mint.clone(), }), 0, ) @@ -477,6 +502,7 @@ extern "C" fn init() { NftEvent::Initialized { config, total_number_of_tokens, + permission_to_mint, }, 0, ) @@ -493,7 +519,15 @@ extern "C" fn handle() { }; let result = match action { - NftAction::Mint => nft_contract.mint(), + NftAction::Mint => { + let msg_source = msg::source(); + let msg_value = msg::value(); + let result = nft_contract.mint(msg_source, msg_value); + if result.is_err() { + msg::send_with_gas(msg_source, "", 0, msg_value).expect("Error in sending value"); + } + result + } NftAction::TransferFrom { from, to, token_id } => { nft_contract.transfer_from(&from, &to, token_id) } @@ -512,6 +546,9 @@ extern "C" fn handle() { NftAction::AddMetadata { token_id, metadata } => { nft_contract.add_metadata(token_id, metadata) } + NftAction::AddUsersForMint { users } => nft_contract.add_users_for_mint(users), + NftAction::DeleteUserForMint { user } => nft_contract.delete_user_for_mint(user), + NftAction::LiftRestrictionMint => nft_contract.lift_restrictions_mint(), }; msg::reply(result, 0).expect("Failed to encode or reply with `Result`."); @@ -548,6 +585,7 @@ impl From for NftState { img_links_and_data, collection_owner, total_number_of_tokens, + permission_to_mint, .. } = value; @@ -573,6 +611,7 @@ impl From for NftState { img_links_and_data, collection_owner, total_number_of_tokens, + permission_to_mint, } } } diff --git a/src/auction.rs b/src/auction.rs index fd669b1..2a2b0f4 100644 --- a/src/auction.rs +++ b/src/auction.rs @@ -13,27 +13,17 @@ impl NftMarketplace { duration_ms: u32, ) -> Result { if !self.collection_to_owner.contains_key(&collection_address) { - return Err(NftMarketplaceError( - "This collection address is not in the marketplace".to_owned(), - )); + return Err(NftMarketplaceError::WrongCollectionAddress); } if self.auctions.contains_key(&(collection_address, token_id)) { - return Err(NftMarketplaceError( - "This nft is already on auction".to_owned(), - )); + return Err(NftMarketplaceError::AlreadyOnAuction); } if self.sales.contains_key(&(collection_address, token_id)) { - return Err(NftMarketplaceError( - "This token is on sale, cancel the sale if you wish to create the auction" - .to_owned(), - )); + return Err(NftMarketplaceError::AlreadyOnSale); } if min_price < self.config.minimum_transfer_value { - return Err(NftMarketplaceError(format!( - "Auction min price must be greater than existential deposit ({})", - self.config.minimum_transfer_value - ))); + return Err(NftMarketplaceError::LessThanExistentialDeposit); } // check token info @@ -73,7 +63,7 @@ impl NftMarketplace { royalty, }); } else { - return Err(NftMarketplaceError("Wrong received reply".to_owned())); + return Err(NftMarketplaceError::WrongReply); } msg::send_with_gas_delayed( @@ -102,14 +92,15 @@ impl NftMarketplace { token_id: u64, ) -> Result { let msg_value = msg::value(); - let auction = self.check_auction(&collection_address, &token_id, &msg_value)?; + let msg_src = msg::source(); + let auction = self.check_auction(&msg_src, &collection_address, &token_id, &msg_value)?; if auction.current_winner != ActorId::zero() { - // use send_with_gas to transfer the value directly to the balance, not to the mailbox. + // use send_with_gas with gas_limit = 0 to transfer the value directly to the balance, not to the mailbox. msg::send_with_gas(auction.current_winner, "", 0, auction.current_price) .expect("Error in sending value"); } - auction.current_winner = msg::source(); + auction.current_winner = msg_src; auction.current_price = msg_value; Ok(NftMarketplaceEvent::BidAdded { @@ -126,55 +117,60 @@ impl NftMarketplace { ) -> Result { let msg_src = msg::source(); if msg_src != exec::program_id() && !self.admins.contains(&msg_src) { - return Err(NftMarketplaceError( - "Only program or admin can send this message.".to_owned(), - )); + return Err(NftMarketplaceError::AccessDenied); } - let (price, current_owner) = - if let Some(auction) = self.auctions.get(&(collection_address, token_id)) { - if auction.ended_at > exec::block_timestamp() { - return Err(NftMarketplaceError( - "The auction must not end before the deadline".to_owned(), - )); - } - if auction.current_winner == ActorId::zero() { - transfer_token( - collection_address, - auction.owner, - token_id, - self.config.gas_for_transfer_token, - ) - .await?; - } else { - transfer_token( - collection_address, - auction.current_winner, - token_id, - self.config.gas_for_transfer_token, - ) - .await?; - - // transfer value to buyer and percent to collection creator - currency_transfer( - auction.collection_owner, - auction.owner, - auction.current_price, - auction.royalty, - self.config.minimum_transfer_value, - ); - } - let price = auction.current_price; - let current_owner = auction.current_winner; - (price, current_owner) - } else { - return Err(NftMarketplaceError("There is no such auction".to_owned())); - }; + let auction = self.auctions.get(&(collection_address, token_id)).ok_or(NftMarketplaceError::ThereIsNoSuchAuction)?; + + if auction.ended_at > exec::block_timestamp() { + return Err(NftMarketplaceError::DeadlineError); + } + if auction.current_winner == ActorId::zero() { + transfer_token( + collection_address, + auction.owner, + token_id, + self.config.gas_for_transfer_token, + ) + .await?; + } else { + transfer_token( + collection_address, + auction.current_winner, + token_id, + self.config.gas_for_transfer_token, + ) + .await?; + + // transfer value to buyer and percent to collection creator + currency_transfer( + auction.collection_owner, + auction.owner, + auction.current_price, + auction.royalty, + self.config.minimum_transfer_value, + ); + } + let price = auction.current_price; + let current_owner = auction.current_winner; + let auction_creator = auction.owner; self.auctions .remove(&(collection_address, token_id)) .expect("Can't be None"); + msg::send( + auction_creator, + Ok::(NftMarketplaceEvent::AuctionClosed { + collection_address, + token_id, + price, + current_owner, + }), + 0, + ) + .expect("Error during send to owner `NftMarketplaceEvent::AuctionClosed`"); + Ok(NftMarketplaceEvent::AuctionClosed { collection_address, token_id, @@ -188,29 +184,23 @@ impl NftMarketplace { collection_address: ActorId, token_id: u64, ) -> Result { - if let Some(auction) = self.auctions.get(&(collection_address, token_id)) { - if auction.owner != msg::source() { - return Err(NftMarketplaceError( - "Only the creator of the auction can send this message".to_owned(), - )); - } - transfer_token( - collection_address, - auction.owner, - token_id, - self.config.gas_for_transfer_token, - ) - .await?; + let auction = self.auctions.get(&(collection_address, token_id)).ok_or(NftMarketplaceError::ThereIsNoSuchAuction)?; - if auction.current_winner != ActorId::zero() { - // use send_with_gas to transfer the value directly to the balance, not to the mailbox. - msg::send_with_gas(auction.current_winner, "", 0, auction.current_price) - .expect("Error in sending value"); - } - } else { - return Err(NftMarketplaceError( - "There is no auction with this collection address and token id".to_owned(), - )); + if auction.owner != msg::source() { + return Err(NftMarketplaceError::AccessDenied); + } + transfer_token( + collection_address, + auction.owner, + token_id, + self.config.gas_for_transfer_token, + ) + .await?; + + if auction.current_winner != ActorId::zero() { + // use send_with_gas to transfer the value directly to the balance, not to the mailbox. + msg::send_with_gas(auction.current_winner, "", 0, auction.current_price) + .expect("Error in sending value"); } self.auctions @@ -225,6 +215,7 @@ impl NftMarketplace { fn check_auction( &mut self, + msg_src: &ActorId, collection_address: &ActorId, token_id: &u64, bid: &u128, @@ -232,18 +223,17 @@ impl NftMarketplace { let auction = if let Some(auction) = self.auctions.get_mut(&(*collection_address, *token_id)) { if auction.ended_at < exec::block_timestamp() { - return Err(NftMarketplaceError("Auction is already ended.".to_owned())); + msg::send_with_gas(*msg_src, "", 0, *bid).expect("Error in sending value"); + return Err(NftMarketplaceError::AuctionClosed); } if *bid <= auction.current_price { - return Err(NftMarketplaceError( - "Less than or equal to the current bid rate.".to_owned(), - )); + msg::send_with_gas(*msg_src, "", 0, *bid).expect("Error in sending value"); + return Err(NftMarketplaceError::LessOrEqualThanBid); } auction } else { - return Err(NftMarketplaceError( - "There is no auction with this collection address and token id".to_owned(), - )); + msg::send_with_gas(*msg_src, "", 0, *bid).expect("Error in sending value"); + return Err(NftMarketplaceError::ThereIsNoSuchAuction); }; Ok(auction) } diff --git a/src/lib.rs b/src/lib.rs index c9c4872..495909b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -241,18 +241,12 @@ impl NftMarketplace { ) -> Result { let msg_src = msg::source(); - let collection_owner = - if let Some((.., owner)) = self.collection_to_owner.get(&collection_address) { - *owner - } else { - return Err(NftMarketplaceError( - "There is no collection with that address".to_owned(), - )); - }; + let (.., collection_owner) = self.collection_to_owner.get(&collection_address).ok_or(NftMarketplaceError::WrongCollectionAddress)?; + if self.admins.contains(&msg_src) { self.collection_to_owner.remove(&collection_address); - } else if collection_owner == msg_src { + } else if *collection_owner == msg_src { let reply = msg::send_with_gas_for_reply_as::>( collection_address, NftAction::CanDelete, @@ -268,15 +262,13 @@ impl NftMarketplace { if answer { self.collection_to_owner.remove(&collection_address); } else { - return Err(NftMarketplaceError("Removal denied".to_owned())); + return Err(NftMarketplaceError::AccessDenied); } } else { - return Err(NftMarketplaceError("Wrong received reply".to_owned())); + return Err(NftMarketplaceError::WrongReply); } } else { - return Err(NftMarketplaceError( - "Only the owner of the collection can send this message".to_owned(), - )); + return Err(NftMarketplaceError::AccessDenied); } Ok(NftMarketplaceEvent::CollectionDeleted { collection_address }) @@ -352,9 +344,7 @@ impl NftMarketplace { if exec::block_timestamp() - time < self.config.time_between_create_collections && !self.admins.contains(user) { - return Err(NftMarketplaceError( - "The time limit for creating a collection has not yet expired.".to_owned(), - )); + return Err(NftMarketplaceError::DeadlineError); } } Ok(()) @@ -362,9 +352,7 @@ impl NftMarketplace { fn check_admin(&self) -> Result<(), NftMarketplaceError> { if !self.admins.contains(&msg::source()) { - return Err(NftMarketplaceError( - "Only admin can send this message".to_owned(), - )); + return Err(NftMarketplaceError::AccessDenied); } Ok(()) } @@ -375,7 +363,7 @@ impl NftMarketplace { ) -> Result { match reply { Ok(result) => Ok(result), - Err(NftError(error_string)) => Err(NftMarketplaceError(error_string.clone())), + Err(_) => Err(NftMarketplaceError::ErrorFromCollection), } } @@ -383,13 +371,7 @@ impl NftMarketplace { &self, type_name: &str, ) -> Result<&TypeCollectionInfo, NftMarketplaceError> { - if let Some(collection_info) = self.type_collections.get(type_name) { - Ok(collection_info) - } else { - Err(NftMarketplaceError( - "There is no collection with this name yet.".to_owned(), - )) - } + self.type_collections.get(type_name).ok_or(NftMarketplaceError::WrongCollectionName) } } diff --git a/src/nft_messages.rs b/src/nft_messages.rs index 2e40613..ff2aeef 100644 --- a/src/nft_messages.rs +++ b/src/nft_messages.rs @@ -75,29 +75,20 @@ pub async fn check_token_info( { // nft should be sellable if !sellable { - return Err(NftMarketplaceError("Nft is not sellable".to_owned())); + return Err(NftMarketplaceError::NotSellable); } // the owner must be the same as the one who wants to sell if token_owner != *msg_src { - return Err(NftMarketplaceError( - "Only the owner of the token can perform this action.".to_owned(), - )); + return Err(NftMarketplaceError::AccessDenied); } // must be approved by the marketplace - if let Some(approve_acc) = approval { - if approve_acc != *address_marketplace { - return Err(NftMarketplaceError( - "No approve to the marketplace".to_owned(), - )); - } - } else { - return Err(NftMarketplaceError( - "No approve to the marketplace".to_owned(), - )); + let approve_acc = approval.ok_or(NftMarketplaceError::NoApproveToMarketplace)?; + if approve_acc != *address_marketplace { + return Err(NftMarketplaceError::NoApproveToMarketplace); } (collection_owner, royalty) } else { - return Err(NftMarketplaceError("Wrong received reply".to_owned())); + return Err(NftMarketplaceError::WrongReply); }; Ok((collection_owner, royalty)) } @@ -105,6 +96,6 @@ pub async fn check_token_info( fn check_reply(reply: Result) -> Result { match reply { Ok(result) => Ok(result), - Err(NftError(error_string)) => Err(NftMarketplaceError(error_string.clone())), + Err(_) => Err(NftMarketplaceError::ErrorFromCollection), } } diff --git a/src/offer.rs b/src/offer.rs index b54bd5c..e47fa5e 100644 --- a/src/offer.rs +++ b/src/offer.rs @@ -11,16 +11,9 @@ impl NftMarketplace { token_id: u64, ) -> Result { let current_price = msg::value(); - if current_price < self.config.minimum_transfer_value { - return Err(NftMarketplaceError(format!( - "The price must be greater than existential deposit ({})", - self.config.minimum_transfer_value - ))); - } + if !self.collection_to_owner.contains_key(&collection_address) { - return Err(NftMarketplaceError( - "This collection address is not in the marketplace".to_owned(), - )); + return Err(NftMarketplaceError::WrongCollectionAddress); } let get_token_info_payload = NftAction::GetTokenInfo { token_id }; @@ -36,15 +29,15 @@ impl NftMarketplace { .expect("Problem with get token info"); match reply { - Err(NftError(error_string)) => Err(NftMarketplaceError(error_string.clone())), + Err(_) => Err(NftMarketplaceError::ErrorFromCollection), Ok(NftEvent::TokenInfoReceived { sellable, .. }) => { if !sellable { - Err(NftMarketplaceError("Nft is not sellable".to_owned())) + Err(NftMarketplaceError::NotSellable) } else { Ok(()) } } - _ => Err(NftMarketplaceError("Wrong received reply".to_owned())), + _ => Err(NftMarketplaceError::WrongReply), }?; let offer = Offer { @@ -82,9 +75,7 @@ impl NftMarketplace { // use send_with_gas to transfer the value directly to the balance, not to the mailbox. msg::send_with_gas(offer.creator, "", 0, *price).expect("Error in sending value"); } else { - return Err(NftMarketplaceError( - "This offer does not exist or you are not the creator of the offer".to_owned(), - )); + return Err(NftMarketplaceError::WrongDataOffer); } self.offers.remove(&offer); @@ -102,21 +93,16 @@ impl NftMarketplace { .sales .contains_key(&(offer.collection_address, offer.token_id)) { - return Err(NftMarketplaceError( - "This token is on sale, cancel the sale if you wish to accept the offer".to_owned(), - )); + return Err(NftMarketplaceError::AlreadyOnSale); } if self .auctions .contains_key(&(offer.collection_address, offer.token_id)) { - return Err(NftMarketplaceError( - "This token is on auction, cancel the auction if you wish to accept the offer" - .to_owned(), - )); + return Err(NftMarketplaceError::AlreadyOnAuction); } if !self.offers.contains_key(&offer) { - return Err(NftMarketplaceError("This offer does not exist".to_owned())); + return Err(NftMarketplaceError::OfferDoesNotExist); } // check token info diff --git a/src/sale.rs b/src/sale.rs index d47ecc0..5bd9578 100644 --- a/src/sale.rs +++ b/src/sale.rs @@ -13,29 +13,19 @@ impl NftMarketplace { ) -> Result { // check that this collection already exists in the marketplace if !self.collection_to_owner.contains_key(&collection_address) { - return Err(NftMarketplaceError( - "This collection address is not in the marketplace".to_owned(), - )); + return Err(NftMarketplaceError::WrongCollectionAddress); } // check that this nft is not sale at this moment if self.sales.contains_key(&(collection_address, token_id)) { - return Err(NftMarketplaceError( - "This nft is already on sale.".to_owned(), - )); + return Err(NftMarketplaceError::AlreadyOnSale); } // check if this nft is currently at auction if self.auctions.contains_key(&(collection_address, token_id)) { - return Err(NftMarketplaceError( - "This token is on auction, cancel the auction if you wish to sale the nft" - .to_owned(), - )); + return Err(NftMarketplaceError::AlreadyOnAuction); } // check that the price is more than the minimum transfer value if price < self.config.minimum_transfer_value { - return Err(NftMarketplaceError(format!( - "The price must be greater than existential deposit ({})", - self.config.minimum_transfer_value - ))); + return Err(NftMarketplaceError::LessThanExistentialDeposit); } // send a message to the nft contract to find out information about the token @@ -76,7 +66,7 @@ impl NftMarketplace { }, ); } else { - return Err(NftMarketplaceError("Wrong received reply".to_owned())); + return Err(NftMarketplaceError::WrongReply); } Ok(NftMarketplaceEvent::SaleNft { @@ -110,15 +100,13 @@ impl NftMarketplace { // in case of successful token transfer, remove the sale from the marketplace self.sales.remove(&(collection_address, token_id)); } else { - return Err(NftMarketplaceError("Wrong received reply".to_owned())); + return Err(NftMarketplaceError::WrongReply); } } else { - return Err(NftMarketplaceError( - "Only the nft owner can cancel the sale.".to_owned(), - )); + return Err(NftMarketplaceError::AccessDenied); } } else { - return Err(NftMarketplaceError("This sale does not exist".to_owned())); + return Err(NftMarketplaceError::SaleDoesNotExist); } Ok(NftMarketplaceEvent::SaleNftCanceled { @@ -177,18 +165,14 @@ impl NftMarketplace { buyer: &ActorId, ) -> Result<(), NftMarketplaceError> { let payment = msg::value(); - let nft = self.sales.get(&(*collection_address, *token_id)); + // check that such a sale exists and check the attached amount - if let Some(nft) = nft { - if payment < nft.price { - msg::send(*buyer, "", payment).expect("Error in sending value"); - return Err(NftMarketplaceError( - "The specified value is less than the price of the token".to_owned(), - )); - } - } else { - return Err(NftMarketplaceError("This sale does not exist".to_owned())); + let nft = self.sales.get(&(*collection_address, *token_id)).ok_or(NftMarketplaceError::SaleDoesNotExist)?; + if payment < nft.price { + msg::send(*buyer, "", payment).expect("Error in sending value"); + return Err(NftMarketplaceError::ValueIsLessThanPrice); } + Ok(()) } } diff --git a/tests/marketplace_node_test.rs b/tests/marketplace_node_test.rs index d922861..d6141ca 100644 --- a/tests/marketplace_node_test.rs +++ b/tests/marketplace_node_test.rs @@ -135,6 +135,7 @@ async fn create_test() -> Result<()> { sellable: Some(0), }, img_links_and_data, + permission_to_mint: None, } .encode(); @@ -296,6 +297,7 @@ async fn sale_test() -> Result<()> { sellable: Some(0), }, img_links_and_data, + permission_to_mint: None, } .encode(); @@ -554,6 +556,7 @@ async fn auction_test() -> Result<()> { sellable: Some(0), }, img_links_and_data, + permission_to_mint: None, } .encode(); @@ -819,6 +822,7 @@ async fn offer_test() -> Result<()> { sellable: Some(0), }, img_links_and_data, + permission_to_mint: None, } .encode(); diff --git a/tests/test_marketplace.rs b/tests/test_marketplace.rs index bc188fb..520adf0 100644 --- a/tests/test_marketplace.rs +++ b/tests/test_marketplace.rs @@ -90,7 +90,7 @@ fn create_success() { println!("CONFIG: {:?}", config); } - let init_nft_payload = get_init_nft_payload(USERS[0].into(), 0, Some(3), 0); + let init_nft_payload = get_init_nft_payload(USERS[0].into(), 0, Some(3), 0, None); // Create collection let res = create_collection( @@ -167,18 +167,10 @@ fn create_failures() { name_simple_nft.clone(), ); - assert!(check_payload( - 0, - &res, - "Only admin can send this message".to_string() - )); + check_marketplace_error(USERS[0], &res, NftMarketplaceError::AccessDenied); let res = add_admin(&marketplace, USERS[0], vec![100.into()]); - assert!(check_payload( - 0, - &res, - "Only admin can send this message".to_string() - )); + check_marketplace_error(USERS[0], &res, NftMarketplaceError::AccessDenied); let res = update_config( &marketplace, @@ -192,11 +184,7 @@ fn create_failures() { Some(11_000_000_000_000), None, ); - assert!(check_payload( - 0, - &res, - "Only admin can send this message".to_string() - )); + check_marketplace_error(USERS[0], &res, NftMarketplaceError::AccessDenied); // Add type of collection let res = add_new_collection( @@ -207,7 +195,7 @@ fn create_failures() { ); assert!(!res.main_failed()); - let init_nft_payload = get_init_nft_payload(USERS[0].into(), 0, Some(3), 0); + let init_nft_payload = get_init_nft_payload(USERS[0].into(), 0, Some(3), 0, None); // Сan only create one collection per hour let res = create_collection( @@ -224,11 +212,7 @@ fn create_failures() { name_simple_nft.clone(), init_nft_payload.encode(), ); - assert!(check_payload( - 0, - &res, - "The time limit for creating a collection has not yet expired.".to_string() - )); + check_marketplace_error(USERS[0], &res, NftMarketplaceError::DeadlineError); // Delete collection let state_reply = marketplace @@ -242,18 +226,10 @@ fn create_failures() { }; let res = delete_collection(&marketplace, USERS[0], 1.into()); assert!(!res.main_failed()); - assert!(check_payload( - 0, - &res, - "There is no collection with that address".to_string() - )); + check_marketplace_error(USERS[0], &res, NftMarketplaceError::WrongCollectionAddress); let res = delete_collection(&marketplace, USERS[1], address_nft); assert!(!res.main_failed()); - assert!(check_payload( - 0, - &res, - "Only the owner of the collection can send this message".to_string() - )); + check_marketplace_error(USERS[1], &res, NftMarketplaceError::AccessDenied); // Mint token let address_nft_array: [u8; 32] = address_nft.into(); @@ -265,7 +241,7 @@ fn create_failures() { let res = delete_collection(&marketplace, USERS[0], address_nft); assert!(!res.main_failed()); - assert!(check_payload(0, &res, "Removal denied".to_string())); + check_marketplace_error(USERS[0], &res, NftMarketplaceError::AccessDenied); } #[test] @@ -303,7 +279,7 @@ fn sale_success() { // Create collection let royalty = 1_000; - let init_nft_payload = get_init_nft_payload(USERS[0].into(), royalty, Some(3), 0); + let init_nft_payload = get_init_nft_payload(USERS[0].into(), royalty, Some(3), 0, None); let res = create_collection( &marketplace, @@ -412,7 +388,7 @@ fn sale_failures() { // Create collection let royalty = 1_000; - let init_nft_payload = get_init_nft_payload(USERS[0].into(), royalty, Some(3), 0); + let init_nft_payload = get_init_nft_payload(USERS[0].into(), royalty, Some(3), 0, None); let res = create_collection( &marketplace, @@ -443,39 +419,23 @@ fn sale_failures() { // low price let res = sale(&marketplace, USERS[1], address_nft, 0, 9_000_000_000_000); assert!(!res.main_failed()); - assert!(check_payload( - 0, - &res, - "The price must be greater than existential deposit (10000000000000)".to_string() - )); + check_marketplace_error(USERS[1], &res, NftMarketplaceError::LessThanExistentialDeposit); // Only owner can send this action let price = 150_000_000_000_000; let res = sale(&marketplace, USERS[2], address_nft, 0, price); assert!(!res.main_failed()); - assert!(check_payload( - 0, - &res, - "Only the owner of the token can perform this action.".to_string() - )); + check_marketplace_error(USERS[2], &res, NftMarketplaceError::AccessDenied); // No approve to the marketplace let res = sale(&marketplace, USERS[1], address_nft, 0, price); assert!(!res.main_failed()); - assert!(check_payload( - 0, - &res, - "No approve to the marketplace".to_string() - )); + check_marketplace_error(USERS[1], &res, NftMarketplaceError::NoApproveToMarketplace); // wrong collection address let res = sale(&marketplace, USERS[1], 1.into(), 0, price); assert!(!res.main_failed()); - assert!(check_payload( - 0, - &res, - "This collection address is not in the marketplace".to_string() - )); + check_marketplace_error(USERS[1], &res, NftMarketplaceError::WrongCollectionAddress); // Successful approve NFT in the collection let addres_marketplace: [u8; 32] = marketplace.id().into(); @@ -502,28 +462,17 @@ fn sale_failures() { // is already on sale let res = sale(&marketplace, USERS[1], address_nft, 0, price); assert!(!res.main_failed()); - assert!(check_payload( - 0, - &res, - "This nft is already on sale.".to_string() - )); + check_marketplace_error(USERS[1], &res, NftMarketplaceError::AlreadyOnSale); // wrong owner let res = cancel_sale(&marketplace, USERS[2], address_nft, 0); assert!(!res.main_failed()); - assert!(check_payload( - 0, - &res, - "Only the nft owner can cancel the sale.".to_string() - )); + check_marketplace_error(USERS[2], &res, NftMarketplaceError::AccessDenied); + // Wrong token_id let res = cancel_sale(&marketplace, USERS[1], address_nft, 1); assert!(!res.main_failed()); - assert!(check_payload( - 0, - &res, - "This sale does not exist".to_string() - )); + check_marketplace_error(USERS[1], &res, NftMarketplaceError::SaleDoesNotExist); // value is less than the price sys.mint_to(USERS[2], price); @@ -532,11 +481,7 @@ fn sale_failures() { let res = buy(&marketplace, USERS[2], address_nft, 0, price - 1); assert!(!res.main_failed()); - assert!(check_payload( - 1, - &res, - "The specified value is less than the price of the token".to_string() - )); + check_marketplace_error(USERS[2], &res, NftMarketplaceError::ValueIsLessThanPrice); let balance = sys.balance_of(1); println!("BALANCE {:?}", balance); @@ -548,12 +493,7 @@ fn sale_failures() { // sale does not exist let res = buy(&marketplace, USERS[2], address_nft, 1, price - 1); assert!(!res.main_failed()); - - assert!(check_payload( - 0, - &res, - "This sale does not exist".to_string() - )); + check_marketplace_error(USERS[2], &res, NftMarketplaceError::SaleDoesNotExist); } #[test] @@ -592,7 +532,7 @@ fn auction_success() { // Create collection let royalty = 1_000; - let init_nft_payload = get_init_nft_payload(USERS[0].into(), royalty, Some(3), 0); + let init_nft_payload = get_init_nft_payload(USERS[0].into(), royalty, Some(3), 0, None); let res = create_collection( &marketplace, @@ -748,7 +688,7 @@ fn auction_cancel() { // Create collection let royalty = 1_000; - let init_nft_payload = get_init_nft_payload(USERS[0].into(), royalty, Some(3), 0); + let init_nft_payload = get_init_nft_payload(USERS[0].into(), royalty, Some(3), 0, None); let res = create_collection( &marketplace, @@ -930,7 +870,7 @@ fn auction_failures() { // Create collection let royalty = 1_000; - let init_nft_payload = get_init_nft_payload(USERS[0].into(), royalty, Some(3), 0); + let init_nft_payload = get_init_nft_payload(USERS[0].into(), royalty, Some(3), 0, None); let res = create_collection( &marketplace, @@ -971,11 +911,7 @@ fn auction_failures() { duration_ms, ); assert!(!res.main_failed()); - assert!(check_payload( - 0, - &res, - "Auction min price must be greater than existential deposit (10000000000000)".to_string() - )); + check_marketplace_error(USERS[1], &res, NftMarketplaceError::LessThanExistentialDeposit); // Only token owner can send let res = create_auction( @@ -987,11 +923,7 @@ fn auction_failures() { duration_ms, ); assert!(!res.main_failed()); - assert!(check_payload( - 0, - &res, - "Only the owner of the token can perform this action.".to_string() - )); + check_marketplace_error(USERS[2], &res, NftMarketplaceError::AccessDenied); // No approve let res = create_auction( @@ -1003,11 +935,7 @@ fn auction_failures() { duration_ms, ); assert!(!res.main_failed()); - assert!(check_payload( - 0, - &res, - "No approve to the marketplace".to_string() - )); + check_marketplace_error(USERS[1], &res, NftMarketplaceError::NoApproveToMarketplace); // Successful approve NFT in the collection let addres_marketplace: [u8; 32] = marketplace.id().into(); @@ -1034,42 +962,33 @@ fn auction_failures() { // No auction with this collection address and token id let res = cancel_auction(&marketplace, USERS[1], address_nft, 1); assert!(!res.main_failed()); - assert!(check_payload( - 0, - &res, - "There is no auction with this collection address and token id".to_string() - )); + check_marketplace_error(USERS[1], &res, NftMarketplaceError::ThereIsNoSuchAuction); // Only the creator of the auction can send cancel_auction let res = cancel_auction(&marketplace, USERS[2], address_nft, 0); assert!(!res.main_failed()); - assert!(check_payload( - 0, - &res, - "Only the creator of the auction can send this message".to_string() - )); + check_marketplace_error(USERS[2], &res, NftMarketplaceError::AccessDenied); let current_balance = 20_000_000_000_000; sys.mint_to(USERS[2], current_balance); let res = add_bid(&marketplace, USERS[2], address_nft, 0, 10_000_000_000_000); assert!(!res.main_failed()); - assert!(check_payload( - 0, - &res, - "Less than or equal to the current bid rate.".to_string() - )); - // let balance = sys.balance_of(USERS[2]); - // assert_eq!(balance, 100_000_000_000_000, "Wrong balance"); + check_marketplace_error(USERS[2], &res, NftMarketplaceError::LessOrEqualThanBid); + + sys.claim_value_from_mailbox(USERS[2]); + let balance = sys.balance_of(USERS[2]); + assert_eq!(balance, 20_000_000_000_000, "Wrong balance"); + sys.spend_blocks(duration_blocks); sys.mint_to(USERS[3], 15_000_000_000_000); let res = add_bid(&marketplace, USERS[3], address_nft, 0, 15_000_000_000_000); assert!(!res.main_failed()); - assert!(check_payload( - 0, - &res, - "There is no auction with this collection address and token id".to_string() - )); + check_marketplace_error(USERS[3], &res, NftMarketplaceError::ThereIsNoSuchAuction); + + sys.claim_value_from_mailbox(USERS[3]); + let balance = sys.balance_of(USERS[3]); + assert_eq!(balance, 15_000_000_000_000, "Wrong balance"); } #[test] @@ -1107,7 +1026,7 @@ fn offer_success() { // Create collection let royalty = 1_000; - let init_nft_payload = get_init_nft_payload(USERS[0].into(), royalty, Some(3), 0); + let init_nft_payload = get_init_nft_payload(USERS[0].into(), royalty, Some(3), 0, None); let res = create_collection( &marketplace, @@ -1231,7 +1150,7 @@ fn offer_failures() { // Create collection let royalty = 1_000; - let init_nft_payload = get_init_nft_payload(USERS[0].into(), royalty, Some(3), 0); + let init_nft_payload = get_init_nft_payload(USERS[0].into(), royalty, Some(3), 0, None); let res = create_collection( &marketplace, @@ -1260,22 +1179,14 @@ fn offer_failures() { sys.mint_to(USERS[2], offer_price); let res = create_offer(&marketplace, USERS[2], address_nft, 0, offer_price); assert!(!res.main_failed()); - assert!(check_payload( - 0, - &res, - "NonFungibleToken: token does not exist".to_string() - )); + check_marketplace_error(USERS[2], &res, NftMarketplaceError::ErrorFromCollection); // wrong collection address let offer_price = 150_000_000_000_000; sys.mint_to(USERS[2], offer_price); let res = create_offer(&marketplace, USERS[2], 1.into(), 0, offer_price); assert!(!res.main_failed()); - assert!(check_payload( - 0, - &res, - "This collection address is not in the marketplace".to_string() - )); + check_marketplace_error(USERS[2], &res, NftMarketplaceError::WrongCollectionAddress); // Successful mint NFT in the new collection let res = nft_collection.send(USERS[1], nft_io::NftAction::Mint); @@ -1322,11 +1233,7 @@ fn offer_failures() { let res = accept_offer(&marketplace, USERS[1], address_nft, 0, USERS[2].into()); let result = &res.decoded_log::>(); println!("RES: {:?}", result); - assert!(check_payload( - 0, - &res, - "This token is on sale, cancel the sale if you wish to accept the offer".to_string() - )); + check_marketplace_error(USERS[1], &res, NftMarketplaceError::AlreadyOnSale); assert!(!res.main_failed()); let res = cancel_sale(&marketplace, USERS[1], address_nft, 0); @@ -1354,11 +1261,8 @@ fn offer_failures() { ); assert!(!res.main_failed()); let res = accept_offer(&marketplace, USERS[1], address_nft, 0, USERS[2].into()); - assert!(check_payload( - 0, - &res, - "This token is on auction, cancel the auction if you wish to accept the offer".to_string() - )); + check_marketplace_error(USERS[1], &res, NftMarketplaceError::AlreadyOnAuction); + assert!(!res.main_failed()); let res = cancel_auction(&marketplace, USERS[1], address_nft, 0); diff --git a/tests/test_music_nft.rs b/tests/test_music_nft.rs index 39f303c..811d9f6 100644 --- a/tests/test_music_nft.rs +++ b/tests/test_music_nft.rs @@ -1,966 +1,966 @@ -use crate::utils::*; -use utils::prelude::*; -mod utils; -use gtest::Program; -use music_nft_io::{ - Action, AdditionalLinks, Config, ImageData, Links, ListenCapability, MusicNftAction, - MusicNftError, MusicNftEvent, MusicNftInit, NftState, StateQuery as StateQueryNft, - StateReply as StateReplyNft, -}; -use nft_marketplace_io::*; - -const USERS: &[u64] = &[5, 6, 7, 8]; - -#[test] -fn successful_basics() { - let sys = utils::initialize_system(); - init_marketplace(&sys); - let marketplace = sys.get_program(1); - let nft_collection_code_id = - sys.submit_code("target/wasm32-unknown-unknown/debug/music_nft.opt.wasm"); - - let state_reply = marketplace - .read_state(StateQuery::CollectionsInfo) - .expect("Unexpected invalid state."); - if let StateReply::CollectionsInfo(state) = state_reply { - assert!(state.is_empty(), "Collection info should be empty"); - println!("Collection info: {:?}", state); - } - // Successful addition of a new collection - let name_simple_nft = "Simple NFT".to_string(); - let res = add_new_collection( - &marketplace, - ADMINS[0], - nft_collection_code_id.into_bytes().into(), - name_simple_nft.clone(), - ); - assert!(!res.main_failed()); - let state_reply = marketplace - .read_state(StateQuery::CollectionsInfo) - .expect("Unexpected invalid state."); - if let StateReply::CollectionsInfo(state) = state_reply { - assert!(!state.is_empty(), "Collection info shouldn't be empty"); - println!("Collection info: {:?}", state); - } - // Successful creation of a new collection - let img_data = ImageData { - limit_copies: Some(1), - description: None, - auto_changing_rules: None, - }; - let links_and_data: Vec<(Links, ImageData)> = (0..10) - .map(|i| { - ( - Links { - img_link: None, - music_link: format!("Img-{}", i), - }, - img_data.clone(), - ) - }) - .collect(); - - let additional_links = Some(AdditionalLinks { - external_url: Some("External link".to_string()), - telegram: None, - xcom: None, - medium: None, - discord: None, - }); - - // Successful creation of a new collection - let init_nft_payload = MusicNftInit { - collection_owner: USERS[0].into(), - config: Config { - name: "User Collection".to_string(), - description: "User Collection".to_string(), - collection_banner: "Collection banner".to_string(), - collection_logo: "Collection logo".to_string(), - collection_tags: vec!["tag1".to_string()], - additional_links, - royalty: 0, - user_mint_limit: 3.into(), - listening_capabilities: ListenCapability::Demo, - payment_for_mint: 0, - transferable: Some(0), - sellable: Some(0), - }, - links_and_data, - }; - - let res = create_collection( - &marketplace, - USERS[0], - name_simple_nft.clone(), - init_nft_payload.encode(), - ); - assert!(!res.main_failed()); - let result = &res.decoded_log::>(); - println!("RES: {:?}", result); - let state_reply = marketplace - .read_state(StateQuery::AllCollections) - .expect("Unexpected invalid state."); - let address_nft = if let StateReply::AllCollections(state) = state_reply { - assert!(!state.is_empty(), "Collections shouldn't be empty"); - println!("Collections: {:?}", state); - state[0].0 - } else { - assert!(false, "Unexpected StateReply variant"); - 0.into() - }; - - let address_nft: [u8; 32] = address_nft.into(); - let nft_collection = sys.get_program(address_nft); - let state_reply = nft_collection - .read_state(StateQueryNft::All) - .expect("Unexpected invalid state."); - if let StateReplyNft::All(state) = state_reply { - assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); - println!("Collection NFT info: {:?}", state); - } - - // Successful change config in the new collection - let config = Config { - name: "My Collection".to_string(), - description: "My Collection".to_string(), - collection_banner: "Collection banner".to_string(), - collection_logo: "Collection logo".to_string(), - collection_tags: vec!["tag1".to_string()], - additional_links: None, - royalty: 0, - user_mint_limit: 3.into(), - listening_capabilities: ListenCapability::Demo, - payment_for_mint: 0, - transferable: Some(0), - sellable: Some(0), - }; - let res = nft_collection.send(USERS[0], MusicNftAction::ChangeConfig { config }); - assert!(!res.main_failed()); - let state_reply = nft_collection - .read_state(StateQueryNft::All) - .expect("Unexpected invalid state."); - if let StateReplyNft::All(state) = state_reply { - assert_eq!(state.config.name, "My Collection".to_string()); - assert_eq!(state.config.description, "My Collection".to_string()); - } - - // Successful mint NFT in the new collection - let res = nft_collection.send(USERS[1], MusicNftAction::Mint); - assert!(!res.main_failed()); - - let state_reply = nft_collection - .read_state(StateQueryNft::All) - .expect("Unexpected invalid state."); - if let StateReplyNft::All(state) = state_reply { - println!("Collection NFT info: {:?}", state); - assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); - let (owner, token_id) = state.owners.get(0).expect("Can't be None"); - assert_eq!(*owner, USERS[1].into(), "Wrong owner"); - assert_eq!(*token_id, vec![0], "Wrong token id"); - } - - // Successful transfer NFT in the collection - let res = nft_collection.send( - USERS[1], - MusicNftAction::Transfer { - to: USERS[2].into(), - token_id: 0, - }, - ); - assert!(!res.main_failed()); - - let state_reply = nft_collection - .read_state(StateQueryNft::All) - .expect("Unexpected invalid state."); - if let StateReplyNft::All(state) = state_reply { - println!("!!!!!!!!!! STATE: {:?}", state); - let (owner, token_id) = state.owners.get(0).expect("Can't be None"); - assert_eq!(*owner, USERS[2].into(), "Wrong owner"); - assert_eq!(*token_id, vec![0], "Wrong token id"); - } - - // Successful approve NFT in the collection (USERS[2] -Approve-> USERS[1]) - let res = nft_collection.send( - USERS[2], - MusicNftAction::Approve { - to: USERS[1].into(), - token_id: 0, - }, - ); - assert!(!res.main_failed()); - - let state_reply = nft_collection - .read_state(StateQueryNft::All) - .expect("Unexpected invalid state."); - if let StateReplyNft::All(state) = state_reply { - let (token_id, approval) = state.token_approvals.get(0).expect("Can't be None"); - assert_eq!(*token_id, 0, "Wrong owner"); - assert_eq!(*approval, USERS[1].into(), "Wrong token id"); - } - // Check approve (USERS[1] -Transfer-> USERS[3]) - let res = nft_collection.send( - USERS[1], - MusicNftAction::TransferFrom { - from: USERS[2].into(), - to: USERS[3].into(), - token_id: 0, - }, - ); - assert!(!res.main_failed()); - - let state_reply = nft_collection - .read_state(StateQueryNft::All) - .expect("Unexpected invalid state."); - if let StateReplyNft::All(state) = state_reply { - println!("STATE OWNERS: {:?}", state.owners); - let (owner, token_id) = state.owners.get(0).expect("Can't be None"); - assert_eq!(*owner, USERS[3].into(), "Wrong owner"); - assert_eq!(*token_id, vec![0], "Wrong token id"); - } - - // Check limit of mint = 3 - let res = nft_collection.send(USERS[3], MusicNftAction::Mint); - assert!(!res.main_failed()); - let res = nft_collection.send(USERS[3], MusicNftAction::Mint); - assert!(!res.main_failed()); - let res = nft_collection.send(USERS[3], MusicNftAction::Mint); - assert!(!res.main_failed()); - let res = nft_collection.send(USERS[3], MusicNftAction::Mint); - assert!(!res.main_failed()); - // let res = res.decoded_log::>()[0]; - //println!("RES {:?}", res); - let message: Result = - Err(MusicNftError("You've exhausted your limit.".to_owned())); - assert!(res.contains(&(USERS[3], message.encode()))); - - // Successful Expand NFT in the collection - let img_data = ImageData { - limit_copies: Some(1), - description: None, - auto_changing_rules: None, - }; - let res = nft_collection.send( - USERS[0], - MusicNftAction::Expand { - additional_links: vec![ - ( - Links { - img_link: None, - music_link: "add_link_1".to_string(), - }, - img_data.clone(), - ), - ( - Links { - img_link: None, - music_link: "add_link_2".to_string(), - }, - img_data.clone(), - ), - ], - }, - ); - assert!(!res.main_failed()); - let state_reply = nft_collection - .read_state(StateQueryNft::All) - .expect("Unexpected invalid state."); - if let StateReplyNft::All(state) = state_reply { - assert_eq!( - state.links_and_data.len(), - 8, - "Wrong length of links_and_data" - ); - println!("STATE: {:?}", state); - } -} - -#[test] -fn failures() { - let sys = utils::initialize_system(); - init_marketplace(&sys); - let marketplace = sys.get_program(1); - let nft_collection_code_id = - sys.submit_code("target/wasm32-unknown-unknown/debug/music_nft.opt.wasm"); - - let name_simple_nft = "Simple NFT".to_string(); - let res = add_new_collection( - &marketplace, - ADMINS[0], - nft_collection_code_id.into_bytes().into(), - name_simple_nft.clone(), - ); - assert!(!res.main_failed()); - - // The mint limit must be greater than zero - let img_data = ImageData { - limit_copies: Some(1), - description: None, - auto_changing_rules: None, - }; - let links_and_data: Vec<(Links, ImageData)> = (0..10) - .map(|i| { - ( - Links { - img_link: None, - music_link: format!("Img-{}", i), - }, - img_data.clone(), - ) - }) - .collect(); - - let additional_links = Some(AdditionalLinks { - external_url: Some("External link".to_string()), - telegram: None, - xcom: None, - medium: None, - discord: None, - }); - - // Successful creation of a new collection - let mut init_nft_payload = MusicNftInit { - collection_owner: USERS[0].into(), - config: Config { - name: "User Collection".to_string(), - description: "User Collection".to_string(), - collection_banner: "Collection banner".to_string(), - collection_logo: "Collection logo".to_string(), - collection_tags: vec!["tag1".to_string()], - additional_links, - royalty: 0, - user_mint_limit: 0.into(), - listening_capabilities: ListenCapability::Demo, - payment_for_mint: 0, - transferable: Some(0), - sellable: Some(0), - }, - links_and_data, - }; - - let res = create_collection( - &marketplace, - USERS[0], - name_simple_nft.clone(), - init_nft_payload.encode(), - ); - assert!(res.main_failed()); - - // There must be at least one link to create a collection - init_nft_payload.config.user_mint_limit = 4.into(); - init_nft_payload.links_and_data = vec![]; - let res = create_collection( - &marketplace, - USERS[0], - name_simple_nft.clone(), - init_nft_payload.encode(), - ); - assert!(res.main_failed()); - - // Limit of copies value is equal to 0 - let img_data = ImageData { - limit_copies: Some(0), - description: None, - auto_changing_rules: None, - }; - init_nft_payload.links_and_data = vec![( - Links { - img_link: None, - music_link: "add_link_1".to_string(), - }, - img_data.clone(), - )]; - - let res = create_collection( - &marketplace, - USERS[0], - name_simple_nft.clone(), - init_nft_payload.encode(), - ); - assert!(res.main_failed()); - - let img_data = ImageData { - limit_copies: Some(1), - description: None, - auto_changing_rules: None, - }; - let links_and_data: Vec<(Links, ImageData)> = (0..5) - .map(|i| { - ( - Links { - img_link: None, - music_link: format!("Img-{}", i), - }, - img_data.clone(), - ) - }) - .collect(); - - init_nft_payload.links_and_data = links_and_data; - let res = create_collection( - &marketplace, - USERS[0], - name_simple_nft.clone(), - init_nft_payload.encode(), - ); - assert!(!res.main_failed()); - - let state_reply = marketplace - .read_state(StateQuery::AllCollections) - .expect("Unexpected invalid state."); - let address_nft = if let StateReply::AllCollections(state) = state_reply { - assert!(!state.is_empty(), "Collections shouldn't be empty"); - println!("Collections: {:?}", state); - state[0].0 - } else { - assert!(false, "Unexpected StateReply variant"); - 0.into() - }; - - let address_nft: [u8; 32] = address_nft.into(); - let nft_collection = sys.get_program(address_nft); - - for _ in 0..4 { - let res = nft_collection.send(USERS[1], MusicNftAction::Mint); - assert!(!res.main_failed()); - } - let res = nft_collection.send(USERS[1], MusicNftAction::Mint); - assert!(check_payload( - 0, - &res, - "You've exhausted your limit.".to_string() - )); - - assert!(!res.main_failed()); - - let res = nft_collection.send(USERS[2], MusicNftAction::Mint); - assert!(!res.main_failed()); - let res = nft_collection.send(USERS[2], MusicNftAction::Mint); - assert!(check_payload(0, &res, "All tokens are minted.".to_string())); - assert!(!res.main_failed()); - - let config = Config { - name: "User Collection".to_string(), - description: "User Collection".to_string(), - collection_banner: "Collection banner".to_string(), - collection_logo: "Collection logo".to_string(), - collection_tags: vec!["tag1".to_string()], - additional_links: None, - royalty: 0, - user_mint_limit: 3.into(), - listening_capabilities: ListenCapability::Demo, - payment_for_mint: 0, - transferable: Some(0), - sellable: Some(0), - }; - let res = nft_collection.send(USERS[0], MusicNftAction::ChangeConfig { config }); - assert!(check_payload( - 0, - &res, - "The collection configuration can no more be changed".to_string() - )); - assert!(!res.main_failed()); - - let state_reply = nft_collection - .read_state(StateQueryNft::All) - .expect("Unexpected invalid state."); - if let StateReplyNft::All(state) = state_reply { - println!("STATE: {:?}", state); - } - - let img_data = ImageData { - limit_copies: Some(4), - description: None, - auto_changing_rules: None, - }; - let res = nft_collection.send( - USERS[0], - MusicNftAction::Expand { - additional_links: vec![( - Links { - img_link: None, - music_link: "New link".to_string(), - }, - img_data.clone(), - )], - }, - ); - assert!(!res.main_failed()); - let res = nft_collection.send(USERS[2], MusicNftAction::Mint); - assert!(!res.main_failed()); - let res = nft_collection.send( - USERS[2], - MusicNftAction::Approve { - to: USERS[3].into(), - token_id: 5, - }, - ); - assert!(!res.main_failed()); - let state_reply = nft_collection - .read_state(StateQueryNft::All) - .expect("Unexpected invalid state."); - if let StateReplyNft::All(state) = state_reply { - println!("STATE: {:?}", state); - } - let res = nft_collection.send( - USERS[2], - MusicNftAction::Transfer { - to: USERS[1].into(), - token_id: 5, - }, - ); - assert!(!res.main_failed()); - let res = nft_collection.send( - USERS[3], - MusicNftAction::TransferFrom { - from: USERS[1].into(), - to: USERS[3].into(), - token_id: 5, - }, - ); - assert!(!res.main_failed()); - let state_reply = nft_collection - .read_state(StateQueryNft::All) - .expect("Unexpected invalid state."); - if let StateReplyNft::All(state) = state_reply { - println!("STATE: {:?}", state); - } - - assert!(check_payload( - 0, - &res, - "Target token_approvals is empty.".to_string() - )); -} - -#[test] -fn check_auto_changing_rules() { - let sys = utils::initialize_system(); - init_marketplace(&sys); - let marketplace = sys.get_program(1); - let nft_collection_code_id = - sys.submit_code("target/wasm32-unknown-unknown/debug/music_nft.opt.wasm"); - - let state_reply = marketplace - .read_state(StateQuery::CollectionsInfo) - .expect("Unexpected invalid state."); - if let StateReply::CollectionsInfo(state) = state_reply { - assert!(state.is_empty(), "Collection info should be empty"); - println!("Collection info: {:?}", state); - } - // Successful addition of a new collection - let name_simple_nft = "Simple NFT".to_string(); - let res = add_new_collection( - &marketplace, - ADMINS[0], - nft_collection_code_id.into_bytes().into(), - name_simple_nft.clone(), - ); - assert!(!res.main_failed()); - let state_reply = marketplace - .read_state(StateQuery::CollectionsInfo) - .expect("Unexpected invalid state."); - if let StateReply::CollectionsInfo(state) = state_reply { - assert!(!state.is_empty(), "Collection info shouldn't be empty"); - println!("Collection info: {:?}", state); - } - let img_data = ImageData { - limit_copies: Some(1), - description: None, - auto_changing_rules: Some(vec![ - (9, Action::ChangeImg("Auto change image".to_string())), - (18, Action::AddMeta("Auto change metadata".to_string())), - ]), - }; - let links_and_data: Vec<(Links, ImageData)> = (0..10) - .map(|i| { - ( - Links { - img_link: None, - music_link: format!("Img-{}", i), - }, - img_data.clone(), - ) - }) - .collect(); - - let additional_links = Some(AdditionalLinks { - external_url: Some("External link".to_string()), - telegram: None, - xcom: None, - medium: None, - discord: None, - }); - - // Successful creation of a new collection - let init_nft_payload = MusicNftInit { - collection_owner: USERS[0].into(), - config: Config { - name: "User Collection".to_string(), - description: "User Collection".to_string(), - collection_banner: "Collection banner".to_string(), - collection_logo: "Collection logo".to_string(), - collection_tags: vec!["tag1".to_string()], - additional_links, - royalty: 0, - user_mint_limit: 3.into(), - listening_capabilities: ListenCapability::Demo, - payment_for_mint: 0, - transferable: Some(0), - sellable: Some(0), - }, - links_and_data, - }; - let res = create_collection( - &marketplace, - USERS[0], - name_simple_nft.clone(), - init_nft_payload.encode(), - ); - assert!(!res.main_failed()); - let result = &res.decoded_log::>(); - println!("RES: {:?}", result); - let state_reply = marketplace - .read_state(StateQuery::AllCollections) - .expect("Unexpected invalid state."); - let address_nft = if let StateReply::AllCollections(state) = state_reply { - assert!(!state.is_empty(), "Collections shouldn't be empty"); - println!("Collections: {:?}", state); - state[0].0 - } else { - assert!(false, "Unexpected StateReply variant"); - 0.into() - }; - - let address_nft: [u8; 32] = address_nft.into(); - let nft_collection = sys.get_program(address_nft); - let state_reply = nft_collection - .read_state(StateQueryNft::All) - .expect("Unexpected invalid state."); - if let StateReplyNft::All(state) = state_reply { - assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); - println!("Collection NFT info: {:?}", state); - } - - // Successful mint NFT in the new collection - let res = nft_collection.send(USERS[1], MusicNftAction::Mint); - assert!(!res.main_failed()); - - let state_reply = nft_collection - .read_state(StateQueryNft::All) - .expect("Unexpected invalid state."); - if let StateReplyNft::All(state) = state_reply { - println!("Collection NFT info: {:?}", state); - assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); - let (owner, token_id) = state.owners.get(0).expect("Can't be None"); - assert_eq!(*owner, USERS[1].into(), "Wrong owner"); - assert_eq!(*token_id, vec![0], "Wrong token id"); - } - - let state = get_state(&nft_collection).unwrap(); - assert_ne!(state.tokens[0].1.img_link, "Auto change image".to_string()); - assert_ne!( - state.tokens[0].1.metadata, - vec!["Auto change metadata".to_string()] - ); - sys.spend_blocks(3); - let state = get_state(&nft_collection).unwrap(); - assert_eq!(state.tokens[0].1.img_link, "Auto change image".to_string()); - assert_ne!( - state.tokens[0].1.metadata, - vec!["Auto change metadata".to_string()] - ); - sys.spend_blocks(6); - let state = get_state(&nft_collection).unwrap(); - assert_eq!(state.tokens[0].1.img_link, "Auto change image".to_string()); - assert_eq!( - state.tokens[0].1.metadata, - vec!["Auto change metadata".to_string()] - ); -} - -#[test] -fn check_transferable() { - let sys = utils::initialize_system(); - init_marketplace(&sys); - let marketplace = sys.get_program(1); - let nft_collection_code_id = - sys.submit_code("target/wasm32-unknown-unknown/debug/music_nft.opt.wasm"); - - // Successful addition of a new collection - let name_simple_nft = "Simple NFT".to_string(); - let res = add_new_collection( - &marketplace, - ADMINS[0], - nft_collection_code_id.into_bytes().into(), - name_simple_nft.clone(), - ); - assert!(!res.main_failed()); - - let img_data = ImageData { - limit_copies: Some(1), - description: None, - auto_changing_rules: None, - }; - let links_and_data: Vec<(Links, ImageData)> = (0..10) - .map(|i| { - ( - Links { - img_link: None, - music_link: format!("Img-{}", i), - }, - img_data.clone(), - ) - }) - .collect(); - - let additional_links = Some(AdditionalLinks { - external_url: Some("External link".to_string()), - telegram: None, - xcom: None, - medium: None, - discord: None, - }); - - // Successful creation of a new collection - let mut init_nft_payload = MusicNftInit { - collection_owner: USERS[0].into(), - config: Config { - name: "User Collection".to_string(), - description: "User Collection".to_string(), - collection_banner: "Collection banner".to_string(), - collection_logo: "Collection logo".to_string(), - collection_tags: vec!["tag1".to_string()], - additional_links, - royalty: 0, - user_mint_limit: 3.into(), - listening_capabilities: ListenCapability::Demo, - payment_for_mint: 0, - transferable: Some(0), - sellable: Some(0), - }, - links_and_data, - }; - - let transferable_time = 9_000; - init_nft_payload.config.transferable = Some(transferable_time); - let res = create_collection( - &marketplace, - USERS[0], - name_simple_nft.clone(), - init_nft_payload.encode(), - ); - assert!(!res.main_failed()); - let result = &res.decoded_log::>(); - println!("RES: {:?}", result); - let state_reply = marketplace - .read_state(StateQuery::AllCollections) - .expect("Unexpected invalid state."); - let address_nft = if let StateReply::AllCollections(state) = state_reply { - assert!(!state.is_empty(), "Collections shouldn't be empty"); - println!("Collections: {:?}", state); - state[0].0 - } else { - assert!(false, "Unexpected StateReply variant"); - 0.into() - }; - - let address_nft: [u8; 32] = address_nft.into(); - let nft_collection = sys.get_program(address_nft); - let state_reply = nft_collection - .read_state(StateQueryNft::All) - .expect("Unexpected invalid state."); - if let StateReplyNft::All(state) = state_reply { - assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); - println!("Collection NFT info: {:?}", state); - } - - // Successful mint NFT in the new collection - let res = nft_collection.send(USERS[1], MusicNftAction::Mint); - assert!(!res.main_failed()); - - // Transfer NFT in the collection - let res = nft_collection.send( - USERS[1], - MusicNftAction::Transfer { - to: USERS[2].into(), - token_id: 0, - }, - ); - assert!(!res.main_failed()); - assert!(check_payload( - 0, - &res, - "NonFungibleToken: transfer will be available after the deadline".to_string() - )); - - sys.spend_blocks(3); - - let res = nft_collection.send( - USERS[1], - MusicNftAction::Transfer { - to: USERS[2].into(), - token_id: 0, - }, - ); - assert!(!res.main_failed()); - let state_reply = nft_collection - .read_state(StateQueryNft::All) - .expect("Unexpected invalid state."); - if let StateReplyNft::All(state) = state_reply { - println!("!!!!!!!!!! STATE: {:?}", state); - let (owner, token_id) = state.owners.get(0).expect("Can't be None"); - assert_eq!(*owner, USERS[2].into(), "Wrong owner"); - assert_eq!(*token_id, vec![0], "Wrong token id"); - } -} - -#[test] -fn check_payment_for_mint() { - let sys = utils::initialize_system(); - init_marketplace(&sys); - let marketplace = sys.get_program(1); - let nft_collection_code_id = - sys.submit_code("target/wasm32-unknown-unknown/debug/music_nft.opt.wasm"); - - let state_reply = marketplace - .read_state(StateQuery::CollectionsInfo) - .expect("Unexpected invalid state."); - if let StateReply::CollectionsInfo(state) = state_reply { - assert!(state.is_empty(), "Collection info should be empty"); - println!("Collection info: {:?}", state); - } - // Successful addition of a new collection - let name_simple_nft = "Simple NFT".to_string(); - let res = add_new_collection( - &marketplace, - ADMINS[0], - nft_collection_code_id.into_bytes().into(), - name_simple_nft.clone(), - ); - assert!(!res.main_failed()); - let state_reply = marketplace - .read_state(StateQuery::CollectionsInfo) - .expect("Unexpected invalid state."); - if let StateReply::CollectionsInfo(state) = state_reply { - assert!(!state.is_empty(), "Collection info shouldn't be empty"); - println!("Collection info: {:?}", state); - } - - // The payment for mint must be greater than existential deposit (10000000000000) - let img_data = ImageData { - limit_copies: Some(1), - description: None, - auto_changing_rules: None, - }; - let links_and_data: Vec<(Links, ImageData)> = (0..10) - .map(|i| { - ( - Links { - img_link: None, - music_link: format!("Img-{}", i), - }, - img_data.clone(), - ) - }) - .collect(); - - let additional_links = Some(AdditionalLinks { - external_url: Some("External link".to_string()), - telegram: None, - xcom: None, - medium: None, - discord: None, - }); - - // Successful creation of a new collection - let mut init_nft_payload = MusicNftInit { - collection_owner: USERS[0].into(), - config: Config { - name: "User Collection".to_string(), - description: "User Collection".to_string(), - collection_banner: "Collection banner".to_string(), - collection_logo: "Collection logo".to_string(), - collection_tags: vec!["tag1".to_string()], - additional_links, - royalty: 0, - user_mint_limit: 3.into(), - listening_capabilities: ListenCapability::Demo, - payment_for_mint: 0, - transferable: Some(0), - sellable: Some(0), - }, - links_and_data, - }; - - let payment_for_mint = 9_000_000_000_000; - init_nft_payload.config.payment_for_mint = payment_for_mint; - let res = create_collection( - &marketplace, - USERS[0], - name_simple_nft.clone(), - init_nft_payload.encode(), - ); - assert!(res.main_failed()); - - // Successful creation of a new collection - let payment_for_mint = 11_000_000_000_000; - init_nft_payload.config.payment_for_mint = payment_for_mint; - let res = create_collection( - &marketplace, - USERS[0], - name_simple_nft.clone(), - init_nft_payload.encode(), - ); - assert!(!res.main_failed()); - - let result = &res.decoded_log::>(); - println!("RES: {:?}", result); - let state_reply = marketplace - .read_state(StateQuery::AllCollections) - .expect("Unexpected invalid state."); - let address_nft = if let StateReply::AllCollections(state) = state_reply { - assert!(!state.is_empty(), "Collections shouldn't be empty"); - println!("Collections: {:?}", state); - state[0].0 - } else { - assert!(false, "Unexpected StateReply variant"); - 0.into() - }; - - let address_nft: [u8; 32] = address_nft.into(); - let nft_collection = sys.get_program(address_nft); - let state_reply = nft_collection - .read_state(StateQueryNft::All) - .expect("Unexpected invalid state."); - if let StateReplyNft::All(state) = state_reply { - assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); - println!("Collection NFT info: {:?}", state); - } - - // Successful mint NFT in the new collection - sys.mint_to(USERS[1], 2 * payment_for_mint); - let res = nft_collection.send_with_value(USERS[1], MusicNftAction::Mint, payment_for_mint); - assert!(!res.main_failed()); - - sys.claim_value_from_mailbox(USERS[0]); - let balance = sys.balance_of(USERS[0]); - assert_eq!(balance, payment_for_mint, "Wrong balance"); - let res = nft_collection.send_with_value(USERS[1], MusicNftAction::Mint, payment_for_mint - 1); - assert!(!res.main_failed()); - - assert!(check_payload( - 0, - &res, - "Incorrectly entered mint fee.".to_string() - )); -} - -fn get_state(nft_collection: &Program) -> Option { - let state_reply = nft_collection - .read_state(StateQueryNft::All) - .expect("Unexpected invalid state."); - if let StateReplyNft::All(state) = state_reply { - return Some(state); - } - None -} +// use crate::utils::*; +// use utils::prelude::*; +// mod utils; +// use gtest::Program; +// use music_nft_io::{ +// Action, AdditionalLinks, Config, ImageData, Links, ListenCapability, MusicNftAction, +// MusicNftError, MusicNftEvent, MusicNftInit, NftState, StateQuery as StateQueryNft, +// StateReply as StateReplyNft, +// }; +// use nft_marketplace_io::*; + +// const USERS: &[u64] = &[5, 6, 7, 8]; + +// #[test] +// fn successful_basics() { +// let sys = utils::initialize_system(); +// init_marketplace(&sys); +// let marketplace = sys.get_program(1); +// let nft_collection_code_id = +// sys.submit_code("target/wasm32-unknown-unknown/debug/music_nft.opt.wasm"); + +// let state_reply = marketplace +// .read_state(StateQuery::CollectionsInfo) +// .expect("Unexpected invalid state."); +// if let StateReply::CollectionsInfo(state) = state_reply { +// assert!(state.is_empty(), "Collection info should be empty"); +// println!("Collection info: {:?}", state); +// } +// // Successful addition of a new collection +// let name_simple_nft = "Simple NFT".to_string(); +// let res = add_new_collection( +// &marketplace, +// ADMINS[0], +// nft_collection_code_id.into_bytes().into(), +// name_simple_nft.clone(), +// ); +// assert!(!res.main_failed()); +// let state_reply = marketplace +// .read_state(StateQuery::CollectionsInfo) +// .expect("Unexpected invalid state."); +// if let StateReply::CollectionsInfo(state) = state_reply { +// assert!(!state.is_empty(), "Collection info shouldn't be empty"); +// println!("Collection info: {:?}", state); +// } +// // Successful creation of a new collection +// let img_data = ImageData { +// limit_copies: Some(1), +// description: None, +// auto_changing_rules: None, +// }; +// let links_and_data: Vec<(Links, ImageData)> = (0..10) +// .map(|i| { +// ( +// Links { +// img_link: None, +// music_link: format!("Img-{}", i), +// }, +// img_data.clone(), +// ) +// }) +// .collect(); + +// let additional_links = Some(AdditionalLinks { +// external_url: Some("External link".to_string()), +// telegram: None, +// xcom: None, +// medium: None, +// discord: None, +// }); + +// // Successful creation of a new collection +// let init_nft_payload = MusicNftInit { +// collection_owner: USERS[0].into(), +// config: Config { +// name: "User Collection".to_string(), +// description: "User Collection".to_string(), +// collection_banner: "Collection banner".to_string(), +// collection_logo: "Collection logo".to_string(), +// collection_tags: vec!["tag1".to_string()], +// additional_links, +// royalty: 0, +// user_mint_limit: 3.into(), +// listening_capabilities: ListenCapability::Demo, +// payment_for_mint: 0, +// transferable: Some(0), +// sellable: Some(0), +// }, +// links_and_data, +// }; + +// let res = create_collection( +// &marketplace, +// USERS[0], +// name_simple_nft.clone(), +// init_nft_payload.encode(), +// ); +// assert!(!res.main_failed()); +// let result = &res.decoded_log::>(); +// println!("RES: {:?}", result); +// let state_reply = marketplace +// .read_state(StateQuery::AllCollections) +// .expect("Unexpected invalid state."); +// let address_nft = if let StateReply::AllCollections(state) = state_reply { +// assert!(!state.is_empty(), "Collections shouldn't be empty"); +// println!("Collections: {:?}", state); +// state[0].0 +// } else { +// assert!(false, "Unexpected StateReply variant"); +// 0.into() +// }; + +// let address_nft: [u8; 32] = address_nft.into(); +// let nft_collection = sys.get_program(address_nft); +// let state_reply = nft_collection +// .read_state(StateQueryNft::All) +// .expect("Unexpected invalid state."); +// if let StateReplyNft::All(state) = state_reply { +// assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); +// println!("Collection NFT info: {:?}", state); +// } + +// // Successful change config in the new collection +// let config = Config { +// name: "My Collection".to_string(), +// description: "My Collection".to_string(), +// collection_banner: "Collection banner".to_string(), +// collection_logo: "Collection logo".to_string(), +// collection_tags: vec!["tag1".to_string()], +// additional_links: None, +// royalty: 0, +// user_mint_limit: 3.into(), +// listening_capabilities: ListenCapability::Demo, +// payment_for_mint: 0, +// transferable: Some(0), +// sellable: Some(0), +// }; +// let res = nft_collection.send(USERS[0], MusicNftAction::ChangeConfig { config }); +// assert!(!res.main_failed()); +// let state_reply = nft_collection +// .read_state(StateQueryNft::All) +// .expect("Unexpected invalid state."); +// if let StateReplyNft::All(state) = state_reply { +// assert_eq!(state.config.name, "My Collection".to_string()); +// assert_eq!(state.config.description, "My Collection".to_string()); +// } + +// // Successful mint NFT in the new collection +// let res = nft_collection.send(USERS[1], MusicNftAction::Mint); +// assert!(!res.main_failed()); + +// let state_reply = nft_collection +// .read_state(StateQueryNft::All) +// .expect("Unexpected invalid state."); +// if let StateReplyNft::All(state) = state_reply { +// println!("Collection NFT info: {:?}", state); +// assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); +// let (owner, token_id) = state.owners.get(0).expect("Can't be None"); +// assert_eq!(*owner, USERS[1].into(), "Wrong owner"); +// assert_eq!(*token_id, vec![0], "Wrong token id"); +// } + +// // Successful transfer NFT in the collection +// let res = nft_collection.send( +// USERS[1], +// MusicNftAction::Transfer { +// to: USERS[2].into(), +// token_id: 0, +// }, +// ); +// assert!(!res.main_failed()); + +// let state_reply = nft_collection +// .read_state(StateQueryNft::All) +// .expect("Unexpected invalid state."); +// if let StateReplyNft::All(state) = state_reply { +// println!("!!!!!!!!!! STATE: {:?}", state); +// let (owner, token_id) = state.owners.get(0).expect("Can't be None"); +// assert_eq!(*owner, USERS[2].into(), "Wrong owner"); +// assert_eq!(*token_id, vec![0], "Wrong token id"); +// } + +// // Successful approve NFT in the collection (USERS[2] -Approve-> USERS[1]) +// let res = nft_collection.send( +// USERS[2], +// MusicNftAction::Approve { +// to: USERS[1].into(), +// token_id: 0, +// }, +// ); +// assert!(!res.main_failed()); + +// let state_reply = nft_collection +// .read_state(StateQueryNft::All) +// .expect("Unexpected invalid state."); +// if let StateReplyNft::All(state) = state_reply { +// let (token_id, approval) = state.token_approvals.get(0).expect("Can't be None"); +// assert_eq!(*token_id, 0, "Wrong owner"); +// assert_eq!(*approval, USERS[1].into(), "Wrong token id"); +// } +// // Check approve (USERS[1] -Transfer-> USERS[3]) +// let res = nft_collection.send( +// USERS[1], +// MusicNftAction::TransferFrom { +// from: USERS[2].into(), +// to: USERS[3].into(), +// token_id: 0, +// }, +// ); +// assert!(!res.main_failed()); + +// let state_reply = nft_collection +// .read_state(StateQueryNft::All) +// .expect("Unexpected invalid state."); +// if let StateReplyNft::All(state) = state_reply { +// println!("STATE OWNERS: {:?}", state.owners); +// let (owner, token_id) = state.owners.get(0).expect("Can't be None"); +// assert_eq!(*owner, USERS[3].into(), "Wrong owner"); +// assert_eq!(*token_id, vec![0], "Wrong token id"); +// } + +// // Check limit of mint = 3 +// let res = nft_collection.send(USERS[3], MusicNftAction::Mint); +// assert!(!res.main_failed()); +// let res = nft_collection.send(USERS[3], MusicNftAction::Mint); +// assert!(!res.main_failed()); +// let res = nft_collection.send(USERS[3], MusicNftAction::Mint); +// assert!(!res.main_failed()); +// let res = nft_collection.send(USERS[3], MusicNftAction::Mint); +// assert!(!res.main_failed()); +// // let res = res.decoded_log::>()[0]; +// //println!("RES {:?}", res); +// let message: Result = +// Err(MusicNftError("You've exhausted your limit.".to_owned())); +// assert!(res.contains(&(USERS[3], message.encode()))); + +// // Successful Expand NFT in the collection +// let img_data = ImageData { +// limit_copies: Some(1), +// description: None, +// auto_changing_rules: None, +// }; +// let res = nft_collection.send( +// USERS[0], +// MusicNftAction::Expand { +// additional_links: vec![ +// ( +// Links { +// img_link: None, +// music_link: "add_link_1".to_string(), +// }, +// img_data.clone(), +// ), +// ( +// Links { +// img_link: None, +// music_link: "add_link_2".to_string(), +// }, +// img_data.clone(), +// ), +// ], +// }, +// ); +// assert!(!res.main_failed()); +// let state_reply = nft_collection +// .read_state(StateQueryNft::All) +// .expect("Unexpected invalid state."); +// if let StateReplyNft::All(state) = state_reply { +// assert_eq!( +// state.links_and_data.len(), +// 8, +// "Wrong length of links_and_data" +// ); +// println!("STATE: {:?}", state); +// } +// } + +// #[test] +// fn failures() { +// let sys = utils::initialize_system(); +// init_marketplace(&sys); +// let marketplace = sys.get_program(1); +// let nft_collection_code_id = +// sys.submit_code("target/wasm32-unknown-unknown/debug/music_nft.opt.wasm"); + +// let name_simple_nft = "Simple NFT".to_string(); +// let res = add_new_collection( +// &marketplace, +// ADMINS[0], +// nft_collection_code_id.into_bytes().into(), +// name_simple_nft.clone(), +// ); +// assert!(!res.main_failed()); + +// // The mint limit must be greater than zero +// let img_data = ImageData { +// limit_copies: Some(1), +// description: None, +// auto_changing_rules: None, +// }; +// let links_and_data: Vec<(Links, ImageData)> = (0..10) +// .map(|i| { +// ( +// Links { +// img_link: None, +// music_link: format!("Img-{}", i), +// }, +// img_data.clone(), +// ) +// }) +// .collect(); + +// let additional_links = Some(AdditionalLinks { +// external_url: Some("External link".to_string()), +// telegram: None, +// xcom: None, +// medium: None, +// discord: None, +// }); + +// // Successful creation of a new collection +// let mut init_nft_payload = MusicNftInit { +// collection_owner: USERS[0].into(), +// config: Config { +// name: "User Collection".to_string(), +// description: "User Collection".to_string(), +// collection_banner: "Collection banner".to_string(), +// collection_logo: "Collection logo".to_string(), +// collection_tags: vec!["tag1".to_string()], +// additional_links, +// royalty: 0, +// user_mint_limit: 0.into(), +// listening_capabilities: ListenCapability::Demo, +// payment_for_mint: 0, +// transferable: Some(0), +// sellable: Some(0), +// }, +// links_and_data, +// }; + +// let res = create_collection( +// &marketplace, +// USERS[0], +// name_simple_nft.clone(), +// init_nft_payload.encode(), +// ); +// assert!(res.main_failed()); + +// // There must be at least one link to create a collection +// init_nft_payload.config.user_mint_limit = 4.into(); +// init_nft_payload.links_and_data = vec![]; +// let res = create_collection( +// &marketplace, +// USERS[0], +// name_simple_nft.clone(), +// init_nft_payload.encode(), +// ); +// assert!(res.main_failed()); + +// // Limit of copies value is equal to 0 +// let img_data = ImageData { +// limit_copies: Some(0), +// description: None, +// auto_changing_rules: None, +// }; +// init_nft_payload.links_and_data = vec![( +// Links { +// img_link: None, +// music_link: "add_link_1".to_string(), +// }, +// img_data.clone(), +// )]; + +// let res = create_collection( +// &marketplace, +// USERS[0], +// name_simple_nft.clone(), +// init_nft_payload.encode(), +// ); +// assert!(res.main_failed()); + +// let img_data = ImageData { +// limit_copies: Some(1), +// description: None, +// auto_changing_rules: None, +// }; +// let links_and_data: Vec<(Links, ImageData)> = (0..5) +// .map(|i| { +// ( +// Links { +// img_link: None, +// music_link: format!("Img-{}", i), +// }, +// img_data.clone(), +// ) +// }) +// .collect(); + +// init_nft_payload.links_and_data = links_and_data; +// let res = create_collection( +// &marketplace, +// USERS[0], +// name_simple_nft.clone(), +// init_nft_payload.encode(), +// ); +// assert!(!res.main_failed()); + +// let state_reply = marketplace +// .read_state(StateQuery::AllCollections) +// .expect("Unexpected invalid state."); +// let address_nft = if let StateReply::AllCollections(state) = state_reply { +// assert!(!state.is_empty(), "Collections shouldn't be empty"); +// println!("Collections: {:?}", state); +// state[0].0 +// } else { +// assert!(false, "Unexpected StateReply variant"); +// 0.into() +// }; + +// let address_nft: [u8; 32] = address_nft.into(); +// let nft_collection = sys.get_program(address_nft); + +// for _ in 0..4 { +// let res = nft_collection.send(USERS[1], MusicNftAction::Mint); +// assert!(!res.main_failed()); +// } +// let res = nft_collection.send(USERS[1], MusicNftAction::Mint); +// assert!(check_payload( +// 0, +// &res, +// "You've exhausted your limit.".to_string() +// )); + +// assert!(!res.main_failed()); + +// let res = nft_collection.send(USERS[2], MusicNftAction::Mint); +// assert!(!res.main_failed()); +// let res = nft_collection.send(USERS[2], MusicNftAction::Mint); +// assert!(check_payload(0, &res, "All tokens are minted.".to_string())); +// assert!(!res.main_failed()); + +// let config = Config { +// name: "User Collection".to_string(), +// description: "User Collection".to_string(), +// collection_banner: "Collection banner".to_string(), +// collection_logo: "Collection logo".to_string(), +// collection_tags: vec!["tag1".to_string()], +// additional_links: None, +// royalty: 0, +// user_mint_limit: 3.into(), +// listening_capabilities: ListenCapability::Demo, +// payment_for_mint: 0, +// transferable: Some(0), +// sellable: Some(0), +// }; +// let res = nft_collection.send(USERS[0], MusicNftAction::ChangeConfig { config }); +// assert!(check_payload( +// 0, +// &res, +// "The collection configuration can no more be changed".to_string() +// )); +// assert!(!res.main_failed()); + +// let state_reply = nft_collection +// .read_state(StateQueryNft::All) +// .expect("Unexpected invalid state."); +// if let StateReplyNft::All(state) = state_reply { +// println!("STATE: {:?}", state); +// } + +// let img_data = ImageData { +// limit_copies: Some(4), +// description: None, +// auto_changing_rules: None, +// }; +// let res = nft_collection.send( +// USERS[0], +// MusicNftAction::Expand { +// additional_links: vec![( +// Links { +// img_link: None, +// music_link: "New link".to_string(), +// }, +// img_data.clone(), +// )], +// }, +// ); +// assert!(!res.main_failed()); +// let res = nft_collection.send(USERS[2], MusicNftAction::Mint); +// assert!(!res.main_failed()); +// let res = nft_collection.send( +// USERS[2], +// MusicNftAction::Approve { +// to: USERS[3].into(), +// token_id: 5, +// }, +// ); +// assert!(!res.main_failed()); +// let state_reply = nft_collection +// .read_state(StateQueryNft::All) +// .expect("Unexpected invalid state."); +// if let StateReplyNft::All(state) = state_reply { +// println!("STATE: {:?}", state); +// } +// let res = nft_collection.send( +// USERS[2], +// MusicNftAction::Transfer { +// to: USERS[1].into(), +// token_id: 5, +// }, +// ); +// assert!(!res.main_failed()); +// let res = nft_collection.send( +// USERS[3], +// MusicNftAction::TransferFrom { +// from: USERS[1].into(), +// to: USERS[3].into(), +// token_id: 5, +// }, +// ); +// assert!(!res.main_failed()); +// let state_reply = nft_collection +// .read_state(StateQueryNft::All) +// .expect("Unexpected invalid state."); +// if let StateReplyNft::All(state) = state_reply { +// println!("STATE: {:?}", state); +// } + +// assert!(check_payload( +// 0, +// &res, +// "Target token_approvals is empty.".to_string() +// )); +// } + +// #[test] +// fn check_auto_changing_rules() { +// let sys = utils::initialize_system(); +// init_marketplace(&sys); +// let marketplace = sys.get_program(1); +// let nft_collection_code_id = +// sys.submit_code("target/wasm32-unknown-unknown/debug/music_nft.opt.wasm"); + +// let state_reply = marketplace +// .read_state(StateQuery::CollectionsInfo) +// .expect("Unexpected invalid state."); +// if let StateReply::CollectionsInfo(state) = state_reply { +// assert!(state.is_empty(), "Collection info should be empty"); +// println!("Collection info: {:?}", state); +// } +// // Successful addition of a new collection +// let name_simple_nft = "Simple NFT".to_string(); +// let res = add_new_collection( +// &marketplace, +// ADMINS[0], +// nft_collection_code_id.into_bytes().into(), +// name_simple_nft.clone(), +// ); +// assert!(!res.main_failed()); +// let state_reply = marketplace +// .read_state(StateQuery::CollectionsInfo) +// .expect("Unexpected invalid state."); +// if let StateReply::CollectionsInfo(state) = state_reply { +// assert!(!state.is_empty(), "Collection info shouldn't be empty"); +// println!("Collection info: {:?}", state); +// } +// let img_data = ImageData { +// limit_copies: Some(1), +// description: None, +// auto_changing_rules: Some(vec![ +// (9, Action::ChangeImg("Auto change image".to_string())), +// (18, Action::AddMeta("Auto change metadata".to_string())), +// ]), +// }; +// let links_and_data: Vec<(Links, ImageData)> = (0..10) +// .map(|i| { +// ( +// Links { +// img_link: None, +// music_link: format!("Img-{}", i), +// }, +// img_data.clone(), +// ) +// }) +// .collect(); + +// let additional_links = Some(AdditionalLinks { +// external_url: Some("External link".to_string()), +// telegram: None, +// xcom: None, +// medium: None, +// discord: None, +// }); + +// // Successful creation of a new collection +// let init_nft_payload = MusicNftInit { +// collection_owner: USERS[0].into(), +// config: Config { +// name: "User Collection".to_string(), +// description: "User Collection".to_string(), +// collection_banner: "Collection banner".to_string(), +// collection_logo: "Collection logo".to_string(), +// collection_tags: vec!["tag1".to_string()], +// additional_links, +// royalty: 0, +// user_mint_limit: 3.into(), +// listening_capabilities: ListenCapability::Demo, +// payment_for_mint: 0, +// transferable: Some(0), +// sellable: Some(0), +// }, +// links_and_data, +// }; +// let res = create_collection( +// &marketplace, +// USERS[0], +// name_simple_nft.clone(), +// init_nft_payload.encode(), +// ); +// assert!(!res.main_failed()); +// let result = &res.decoded_log::>(); +// println!("RES: {:?}", result); +// let state_reply = marketplace +// .read_state(StateQuery::AllCollections) +// .expect("Unexpected invalid state."); +// let address_nft = if let StateReply::AllCollections(state) = state_reply { +// assert!(!state.is_empty(), "Collections shouldn't be empty"); +// println!("Collections: {:?}", state); +// state[0].0 +// } else { +// assert!(false, "Unexpected StateReply variant"); +// 0.into() +// }; + +// let address_nft: [u8; 32] = address_nft.into(); +// let nft_collection = sys.get_program(address_nft); +// let state_reply = nft_collection +// .read_state(StateQueryNft::All) +// .expect("Unexpected invalid state."); +// if let StateReplyNft::All(state) = state_reply { +// assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); +// println!("Collection NFT info: {:?}", state); +// } + +// // Successful mint NFT in the new collection +// let res = nft_collection.send(USERS[1], MusicNftAction::Mint); +// assert!(!res.main_failed()); + +// let state_reply = nft_collection +// .read_state(StateQueryNft::All) +// .expect("Unexpected invalid state."); +// if let StateReplyNft::All(state) = state_reply { +// println!("Collection NFT info: {:?}", state); +// assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); +// let (owner, token_id) = state.owners.get(0).expect("Can't be None"); +// assert_eq!(*owner, USERS[1].into(), "Wrong owner"); +// assert_eq!(*token_id, vec![0], "Wrong token id"); +// } + +// let state = get_state(&nft_collection).unwrap(); +// assert_ne!(state.tokens[0].1.img_link, "Auto change image".to_string()); +// assert_ne!( +// state.tokens[0].1.metadata, +// vec!["Auto change metadata".to_string()] +// ); +// sys.spend_blocks(3); +// let state = get_state(&nft_collection).unwrap(); +// assert_eq!(state.tokens[0].1.img_link, "Auto change image".to_string()); +// assert_ne!( +// state.tokens[0].1.metadata, +// vec!["Auto change metadata".to_string()] +// ); +// sys.spend_blocks(6); +// let state = get_state(&nft_collection).unwrap(); +// assert_eq!(state.tokens[0].1.img_link, "Auto change image".to_string()); +// assert_eq!( +// state.tokens[0].1.metadata, +// vec!["Auto change metadata".to_string()] +// ); +// } + +// #[test] +// fn check_transferable() { +// let sys = utils::initialize_system(); +// init_marketplace(&sys); +// let marketplace = sys.get_program(1); +// let nft_collection_code_id = +// sys.submit_code("target/wasm32-unknown-unknown/debug/music_nft.opt.wasm"); + +// // Successful addition of a new collection +// let name_simple_nft = "Simple NFT".to_string(); +// let res = add_new_collection( +// &marketplace, +// ADMINS[0], +// nft_collection_code_id.into_bytes().into(), +// name_simple_nft.clone(), +// ); +// assert!(!res.main_failed()); + +// let img_data = ImageData { +// limit_copies: Some(1), +// description: None, +// auto_changing_rules: None, +// }; +// let links_and_data: Vec<(Links, ImageData)> = (0..10) +// .map(|i| { +// ( +// Links { +// img_link: None, +// music_link: format!("Img-{}", i), +// }, +// img_data.clone(), +// ) +// }) +// .collect(); + +// let additional_links = Some(AdditionalLinks { +// external_url: Some("External link".to_string()), +// telegram: None, +// xcom: None, +// medium: None, +// discord: None, +// }); + +// // Successful creation of a new collection +// let mut init_nft_payload = MusicNftInit { +// collection_owner: USERS[0].into(), +// config: Config { +// name: "User Collection".to_string(), +// description: "User Collection".to_string(), +// collection_banner: "Collection banner".to_string(), +// collection_logo: "Collection logo".to_string(), +// collection_tags: vec!["tag1".to_string()], +// additional_links, +// royalty: 0, +// user_mint_limit: 3.into(), +// listening_capabilities: ListenCapability::Demo, +// payment_for_mint: 0, +// transferable: Some(0), +// sellable: Some(0), +// }, +// links_and_data, +// }; + +// let transferable_time = 9_000; +// init_nft_payload.config.transferable = Some(transferable_time); +// let res = create_collection( +// &marketplace, +// USERS[0], +// name_simple_nft.clone(), +// init_nft_payload.encode(), +// ); +// assert!(!res.main_failed()); +// let result = &res.decoded_log::>(); +// println!("RES: {:?}", result); +// let state_reply = marketplace +// .read_state(StateQuery::AllCollections) +// .expect("Unexpected invalid state."); +// let address_nft = if let StateReply::AllCollections(state) = state_reply { +// assert!(!state.is_empty(), "Collections shouldn't be empty"); +// println!("Collections: {:?}", state); +// state[0].0 +// } else { +// assert!(false, "Unexpected StateReply variant"); +// 0.into() +// }; + +// let address_nft: [u8; 32] = address_nft.into(); +// let nft_collection = sys.get_program(address_nft); +// let state_reply = nft_collection +// .read_state(StateQueryNft::All) +// .expect("Unexpected invalid state."); +// if let StateReplyNft::All(state) = state_reply { +// assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); +// println!("Collection NFT info: {:?}", state); +// } + +// // Successful mint NFT in the new collection +// let res = nft_collection.send(USERS[1], MusicNftAction::Mint); +// assert!(!res.main_failed()); + +// // Transfer NFT in the collection +// let res = nft_collection.send( +// USERS[1], +// MusicNftAction::Transfer { +// to: USERS[2].into(), +// token_id: 0, +// }, +// ); +// assert!(!res.main_failed()); +// assert!(check_payload( +// 0, +// &res, +// "NonFungibleToken: transfer will be available after the deadline".to_string() +// )); + +// sys.spend_blocks(3); + +// let res = nft_collection.send( +// USERS[1], +// MusicNftAction::Transfer { +// to: USERS[2].into(), +// token_id: 0, +// }, +// ); +// assert!(!res.main_failed()); +// let state_reply = nft_collection +// .read_state(StateQueryNft::All) +// .expect("Unexpected invalid state."); +// if let StateReplyNft::All(state) = state_reply { +// println!("!!!!!!!!!! STATE: {:?}", state); +// let (owner, token_id) = state.owners.get(0).expect("Can't be None"); +// assert_eq!(*owner, USERS[2].into(), "Wrong owner"); +// assert_eq!(*token_id, vec![0], "Wrong token id"); +// } +// } + +// #[test] +// fn check_payment_for_mint() { +// let sys = utils::initialize_system(); +// init_marketplace(&sys); +// let marketplace = sys.get_program(1); +// let nft_collection_code_id = +// sys.submit_code("target/wasm32-unknown-unknown/debug/music_nft.opt.wasm"); + +// let state_reply = marketplace +// .read_state(StateQuery::CollectionsInfo) +// .expect("Unexpected invalid state."); +// if let StateReply::CollectionsInfo(state) = state_reply { +// assert!(state.is_empty(), "Collection info should be empty"); +// println!("Collection info: {:?}", state); +// } +// // Successful addition of a new collection +// let name_simple_nft = "Simple NFT".to_string(); +// let res = add_new_collection( +// &marketplace, +// ADMINS[0], +// nft_collection_code_id.into_bytes().into(), +// name_simple_nft.clone(), +// ); +// assert!(!res.main_failed()); +// let state_reply = marketplace +// .read_state(StateQuery::CollectionsInfo) +// .expect("Unexpected invalid state."); +// if let StateReply::CollectionsInfo(state) = state_reply { +// assert!(!state.is_empty(), "Collection info shouldn't be empty"); +// println!("Collection info: {:?}", state); +// } + +// // The payment for mint must be greater than existential deposit (10000000000000) +// let img_data = ImageData { +// limit_copies: Some(1), +// description: None, +// auto_changing_rules: None, +// }; +// let links_and_data: Vec<(Links, ImageData)> = (0..10) +// .map(|i| { +// ( +// Links { +// img_link: None, +// music_link: format!("Img-{}", i), +// }, +// img_data.clone(), +// ) +// }) +// .collect(); + +// let additional_links = Some(AdditionalLinks { +// external_url: Some("External link".to_string()), +// telegram: None, +// xcom: None, +// medium: None, +// discord: None, +// }); + +// // Successful creation of a new collection +// let mut init_nft_payload = MusicNftInit { +// collection_owner: USERS[0].into(), +// config: Config { +// name: "User Collection".to_string(), +// description: "User Collection".to_string(), +// collection_banner: "Collection banner".to_string(), +// collection_logo: "Collection logo".to_string(), +// collection_tags: vec!["tag1".to_string()], +// additional_links, +// royalty: 0, +// user_mint_limit: 3.into(), +// listening_capabilities: ListenCapability::Demo, +// payment_for_mint: 0, +// transferable: Some(0), +// sellable: Some(0), +// }, +// links_and_data, +// }; + +// let payment_for_mint = 9_000_000_000_000; +// init_nft_payload.config.payment_for_mint = payment_for_mint; +// let res = create_collection( +// &marketplace, +// USERS[0], +// name_simple_nft.clone(), +// init_nft_payload.encode(), +// ); +// assert!(res.main_failed()); + +// // Successful creation of a new collection +// let payment_for_mint = 11_000_000_000_000; +// init_nft_payload.config.payment_for_mint = payment_for_mint; +// let res = create_collection( +// &marketplace, +// USERS[0], +// name_simple_nft.clone(), +// init_nft_payload.encode(), +// ); +// assert!(!res.main_failed()); + +// let result = &res.decoded_log::>(); +// println!("RES: {:?}", result); +// let state_reply = marketplace +// .read_state(StateQuery::AllCollections) +// .expect("Unexpected invalid state."); +// let address_nft = if let StateReply::AllCollections(state) = state_reply { +// assert!(!state.is_empty(), "Collections shouldn't be empty"); +// println!("Collections: {:?}", state); +// state[0].0 +// } else { +// assert!(false, "Unexpected StateReply variant"); +// 0.into() +// }; + +// let address_nft: [u8; 32] = address_nft.into(); +// let nft_collection = sys.get_program(address_nft); +// let state_reply = nft_collection +// .read_state(StateQueryNft::All) +// .expect("Unexpected invalid state."); +// if let StateReplyNft::All(state) = state_reply { +// assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); +// println!("Collection NFT info: {:?}", state); +// } + +// // Successful mint NFT in the new collection +// sys.mint_to(USERS[1], 2 * payment_for_mint); +// let res = nft_collection.send_with_value(USERS[1], MusicNftAction::Mint, payment_for_mint); +// assert!(!res.main_failed()); + +// sys.claim_value_from_mailbox(USERS[0]); +// let balance = sys.balance_of(USERS[0]); +// assert_eq!(balance, payment_for_mint, "Wrong balance"); +// let res = nft_collection.send_with_value(USERS[1], MusicNftAction::Mint, payment_for_mint - 1); +// assert!(!res.main_failed()); + +// assert!(check_payload( +// 0, +// &res, +// "Incorrectly entered mint fee.".to_string() +// )); +// } + +// fn get_state(nft_collection: &Program) -> Option { +// let state_reply = nft_collection +// .read_state(StateQueryNft::All) +// .expect("Unexpected invalid state."); +// if let StateReplyNft::All(state) = state_reply { +// return Some(state); +// } +// None +// } diff --git a/tests/test_nft.rs b/tests/test_nft.rs index 795da39..96338dd 100644 --- a/tests/test_nft.rs +++ b/tests/test_nft.rs @@ -42,7 +42,7 @@ fn successful_basics() { println!("Collection info: {:?}", state); } // Successful creation of a new collection - let init_nft_payload = get_init_nft_payload(USERS[0].into(), 0, Some(3), 0); + let init_nft_payload = get_init_nft_payload(USERS[0].into(), 0, Some(3), 0, None); let res = create_collection( &marketplace, @@ -182,11 +182,9 @@ fn successful_basics() { assert!(!res.main_failed()); let res = nft_collection.send(USERS[3], NftAction::Mint); assert!(!res.main_failed()); - // let res = res.decoded_log::>()[0]; - //println!("RES {:?}", res); - let message: Result = - Err(NftError("You've exhausted your limit.".to_owned())); - assert!(res.contains(&(USERS[3], message.encode()))); + let result = &res.decoded_log::>()[0]; + println!("RES {:?}", result); + assert!(res.contains(&(USERS[3], Err::(NftError::ExhaustedLimit).encode()))); // Successful Expand NFT in the collection let img_data = ImageData { @@ -234,7 +232,7 @@ fn failures() { assert!(!res.main_failed()); // The mint limit must be greater than zero - let mut init_nft_payload = get_init_nft_payload(USERS[0].into(), 0, Some(0), 0); + let mut init_nft_payload = get_init_nft_payload(USERS[0].into(), 0, Some(0), 0, None); let res = create_collection( &marketplace, @@ -306,18 +304,14 @@ fn failures() { assert!(!res.main_failed()); } let res = nft_collection.send(USERS[1], NftAction::Mint); - assert!(check_payload( - 0, - &res, - "You've exhausted your limit.".to_string() - )); + check_nft_error(USERS[1], &res, NftError::ExhaustedLimit); assert!(!res.main_failed()); let res = nft_collection.send(USERS[2], NftAction::Mint); assert!(!res.main_failed()); let res = nft_collection.send(USERS[2], NftAction::Mint); - assert!(check_payload(0, &res, "All tokens are minted.".to_string())); + check_nft_error(USERS[2], &res, NftError::AllTokensMinted); assert!(!res.main_failed()); let config = Config { @@ -334,11 +328,7 @@ fn failures() { sellable: Some(0), }; let res = nft_collection.send(USERS[0], NftAction::ChangeConfig { config }); - assert!(check_payload( - 0, - &res, - "The collection configuration can no more be changed".to_string() - )); + check_nft_error(USERS[0], &res, NftError::ConfigCannotBeChanged); assert!(!res.main_failed()); let state_reply = nft_collection @@ -398,12 +388,7 @@ fn failures() { if let StateReplyNft::All(state) = state_reply { println!("STATE: {:?}", state); } - - assert!(check_payload( - 0, - &res, - "Target token_approvals is empty.".to_string() - )); + check_nft_error(USERS[3], &res, NftError::AccessDenied); } #[test] @@ -473,6 +458,7 @@ fn check_auto_changing_rules() { sellable: Some(0), }, img_links_and_data, + permission_to_mint: None, }; let res = create_collection( &marketplace, @@ -560,7 +546,7 @@ fn check_transferable() { ); assert!(!res.main_failed()); - let mut init_nft_payload = get_init_nft_payload(USERS[0].into(), 0, Some(3), 0); + let mut init_nft_payload = get_init_nft_payload(USERS[0].into(), 0, Some(3), 0, None); let transferable_time = 9_000; init_nft_payload.config.transferable = Some(transferable_time); let res = create_collection( @@ -607,11 +593,7 @@ fn check_transferable() { }, ); assert!(!res.main_failed()); - assert!(check_payload( - 0, - &res, - "NonFungibleToken: transfer will be available after the deadline".to_string() - )); + check_nft_error(USERS[1], &res, NftError::NotTransferable); sys.spend_blocks(3); @@ -667,7 +649,7 @@ fn check_payment_for_mint() { } // The payment for mint must be greater than existential deposit (10000000000000) - let mut init_nft_payload = get_init_nft_payload(USERS[0].into(), 0, Some(3), 0); + let mut init_nft_payload = get_init_nft_payload(USERS[0].into(), 0, Some(3), 0, None); let payment_for_mint = 9_000_000_000_000; init_nft_payload.config.payment_for_mint = payment_for_mint; let res = create_collection( @@ -723,12 +705,118 @@ fn check_payment_for_mint() { assert_eq!(balance, payment_for_mint, "Wrong balance"); let res = nft_collection.send_with_value(USERS[1], NftAction::Mint, payment_for_mint - 1); assert!(!res.main_failed()); + check_nft_error(USERS[1], &res, NftError::WrongValue); +} + +#[test] +fn permission_to_mint() { + let sys = utils::initialize_system(); + init_marketplace(&sys); + let marketplace = sys.get_program(1); + let nft_collection_code_id = + sys.submit_code("target/wasm32-unknown-unknown/debug/nft.opt.wasm"); + + let state_reply = marketplace + .read_state(StateQuery::CollectionsInfo) + .expect("Unexpected invalid state."); + if let StateReply::CollectionsInfo(state) = state_reply { + assert!(state.is_empty(), "Collection info should be empty"); + println!("Collection info: {:?}", state); + } + // Successful addition of a new collection + let name_simple_nft = "Simple NFT".to_string(); + let res = add_new_collection( + &marketplace, + ADMINS[0], + nft_collection_code_id.into_bytes().into(), + name_simple_nft.clone(), + ); + assert!(!res.main_failed()); + let state_reply = marketplace + .read_state(StateQuery::CollectionsInfo) + .expect("Unexpected invalid state."); + if let StateReply::CollectionsInfo(state) = state_reply { + assert!(!state.is_empty(), "Collection info shouldn't be empty"); + println!("Collection info: {:?}", state); + } + // Successful creation of a new collection + let init_nft_payload = + get_init_nft_payload(USERS[0].into(), 0, Some(3), 0, Some(vec![])); + + let res = create_collection( + &marketplace, + USERS[0], + name_simple_nft.clone(), + init_nft_payload.encode(), + ); + assert!(!res.main_failed()); + + let state_reply = marketplace + .read_state(StateQuery::AllCollections) + .expect("Unexpected invalid state."); + let address_nft = if let StateReply::AllCollections(state) = state_reply { + assert!(!state.is_empty(), "Collections shouldn't be empty"); + println!("Collections: {:?}", state); + state[0].0 + } else { + assert!(false, "Unexpected StateReply variant"); + 0.into() + }; + + let address_nft: [u8; 32] = address_nft.into(); + let nft_collection = sys.get_program(address_nft); + let state_reply = nft_collection + .read_state(StateQueryNft::All) + .expect("Unexpected invalid state."); + if let StateReplyNft::All(state) = state_reply { + assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); + } + + // Succes mint NFT from admin + let res = nft_collection.send(USERS[1], NftAction::Mint); + assert!(!res.main_failed()); + + // Fail mint NFT from user + let res = nft_collection.send(USERS[1], NftAction::Mint); + assert!(!res.main_failed()); + + check_nft_error(USERS[1], &res, NftError::AccessDenied); + + let res = nft_collection.send( + USERS[0], + NftAction::AddUsersForMint { + users: vec![USERS[1].into()], + }, + ); + assert!(!res.main_failed()); + + // Success mint + let res = nft_collection.send(USERS[1], NftAction::Mint); + assert!(!res.main_failed()); + + let state_reply = nft_collection + .read_state(StateQueryNft::All) + .expect("Unexpected invalid state."); + if let StateReplyNft::All(state) = state_reply { + println!("Collection NFT info: {:?}", state); + assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); + let (owner, token_id) = state.owners.get(0).expect("Can't be None"); + assert_eq!(*owner, USERS[1].into(), "Wrong owner"); + assert_eq!(*token_id, vec![0], "Wrong token id"); + } + let res = nft_collection.send( + USERS[0], + NftAction::DeleteUserForMint { + user: USERS[1].into(), + }, + ); + assert!(!res.main_failed()); + + // Fail mint NFT in the new collection + let res = nft_collection.send(USERS[1], NftAction::Mint); + assert!(!res.main_failed()); - assert!(check_payload( - 0, - &res, - "Incorrectly entered mint fee.".to_string() - )); + check_nft_error(USERS[1], &res, NftError::AccessDenied); } fn get_state(nft_collection: &Program) -> Option { diff --git a/tests/utils/mod.rs b/tests/utils/mod.rs index 1f00213..2d7f954 100644 --- a/tests/utils/mod.rs +++ b/tests/utils/mod.rs @@ -1,7 +1,7 @@ -use gstd::{ActorId, CodeId}; +use gstd::{ActorId, CodeId, Encode}; use gtest::{Program, RunResult, System}; -use nft_io::{Config, ImageData, NftInit}; -use nft_marketplace_io::{NftMarketplaceAction, NftMarketplaceInit, Offer}; +use nft_io::{Config, ImageData, NftError, NftEvent, NftInit}; +use nft_marketplace_io::{NftMarketplaceAction, NftMarketplaceInit, NftMarketplaceError, NftMarketplaceEvent, Offer}; mod common; pub mod prelude; @@ -229,11 +229,11 @@ pub fn delete_collection( pub fn delete_admin(marketplace: &Program, admin: u64, user: ActorId) -> RunResult { marketplace.send(admin, NftMarketplaceAction::DeleteAdmin { user }) } -pub fn check_payload(log_number: usize, result: &RunResult, message: String) -> bool { - result.log()[log_number] - .payload() - .windows(message.as_bytes().len()) - .any(|window| window == message.as_bytes()) +pub fn check_nft_error(from: u64, result: &RunResult, error: NftError){ + assert!(result.contains(&(from, Err::(error).encode()))); +} +pub fn check_marketplace_error(from: u64, result: &RunResult, error: NftMarketplaceError ){ + assert!(result.contains(&(from, Err::(error).encode()))); } pub fn get_init_nft_payload( @@ -241,6 +241,7 @@ pub fn get_init_nft_payload( royalty: u16, user_mint_limit: Option, payment_for_mint: u128, + permission_to_mint: Option>, ) -> NftInit { let img_data = ImageData { limit_copies: Some(1), @@ -266,6 +267,7 @@ pub fn get_init_nft_payload( sellable: Some(0), }, img_links_and_data, + permission_to_mint, } } // pub fn get_state( From 0d8f0c1a0c87fec39112248ec2cf92b55de762c2 Mon Sep 17 00:00:00 2001 From: MedovTimur Date: Sat, 17 Feb 2024 20:45:39 +0300 Subject: [PATCH 3/8] update marketplace and nft --- nft/src/lib.rs | 42 ++++++++++++++++++++++++++++++--------- src/auction.rs | 10 ++++++++-- src/lib.rs | 10 +++++++--- src/sale.rs | 5 ++++- tests/test_marketplace.rs | 12 +++++++++-- tests/test_nft.rs | 10 ++++++---- tests/utils/mod.rs | 13 ++++++++---- 7 files changed, 77 insertions(+), 25 deletions(-) diff --git a/nft/src/lib.rs b/nft/src/lib.rs index b6650bd..57e9653 100644 --- a/nft/src/lib.rs +++ b/nft/src/lib.rs @@ -119,7 +119,10 @@ impl NftContract { // checking for the possibility to transfer self.can_transfer(from, to, &token_id)?; - let nft = self.tokens.get_mut(&token_id).ok_or(NftError::TokenDoesNotExist)?; + let nft = self + .tokens + .get_mut(&token_id) + .ok_or(NftError::TokenDoesNotExist)?; let tokens = self .owners .get_mut(from) @@ -163,12 +166,18 @@ impl NftContract { if nft_info.owner != msg::source() { return Err(NftError::AccessDenied); } - let _ = self.token_approvals.remove(&token_id).ok_or(NftError::NoApproval); + let _ = self + .token_approvals + .remove(&token_id) + .ok_or(NftError::NoApproval); Ok(NftEvent::ApprovalRevoked { token_id }) } fn get_token_info(&self, token_id: NftId) -> Result { - let nft = self.tokens.get(&token_id).ok_or(NftError::TokenDoesNotExist)?; + let nft = self + .tokens + .get(&token_id) + .ok_or(NftError::TokenDoesNotExist)?; let can_sell = self .config @@ -240,7 +249,10 @@ impl NftContract { return Err(NftError::AccessDenied); } - let nft = self.tokens.get_mut(&token_id).ok_or(NftError::TokenDoesNotExist)?; + let nft = self + .tokens + .get_mut(&token_id) + .ok_or(NftError::TokenDoesNotExist)?; nft.media_url = img_link.clone(); msg::send( @@ -259,7 +271,10 @@ impl NftContract { if exec::program_id() != msg::source() { return Err(NftError::AccessDenied); } - let nft = self.tokens.get_mut(&token_id).ok_or(NftError::TokenDoesNotExist)?; + let nft = self + .tokens + .get_mut(&token_id) + .ok_or(NftError::TokenDoesNotExist)?; nft.metadata.push(metadata.clone()); msg::send( @@ -296,7 +311,10 @@ impl NftContract { fn delete_user_for_mint(&mut self, user: ActorId) -> Result { let msg_src = msg::source(); self.check_collection_owner(msg_src)?; - let allowed_users = self.permission_to_mint.as_mut().ok_or(NftError::NoListOfRestriction)?; + let allowed_users = self + .permission_to_mint + .as_mut() + .ok_or(NftError::NoListOfRestriction)?; if let Some(pos) = allowed_users .iter() .position(|allowed_user| *allowed_user == user) @@ -386,7 +404,10 @@ impl NftContract { // approval check fn check_approve(&self, user: &ActorId, token_id: &NftId) -> Result<(), NftError> { - let approved_account = self.token_approvals.get(token_id).ok_or(NftError::AccessDenied)?; + let approved_account = self + .token_approvals + .get(token_id) + .ok_or(NftError::AccessDenied)?; if approved_account != user { return Err(NftError::AccessDenied); } @@ -396,7 +417,10 @@ impl NftContract { // doing all the checks to verify that the transfer can be made fn can_transfer(&self, from: &ActorId, to: &ActorId, token_id: &NftId) -> Result<(), NftError> { - let nft = self.tokens.get(token_id).ok_or(NftError::TokenDoesNotExist)?; + let nft = self + .tokens + .get(token_id) + .ok_or(NftError::TokenDoesNotExist)?; let owner = nft.owner; if owner != *from { @@ -469,7 +493,7 @@ extern "C" fn init() { let total_number_of_tokens = sum_limit_copies(&img_links_and_data); if let Some(users) = permission_to_mint.as_mut() { - if !users.contains(&collection_owner){ + if !users.contains(&collection_owner) { users.push(collection_owner); } } diff --git a/src/auction.rs b/src/auction.rs index 2a2b0f4..e34836b 100644 --- a/src/auction.rs +++ b/src/auction.rs @@ -120,7 +120,10 @@ impl NftMarketplace { return Err(NftMarketplaceError::AccessDenied); } - let auction = self.auctions.get(&(collection_address, token_id)).ok_or(NftMarketplaceError::ThereIsNoSuchAuction)?; + let auction = self + .auctions + .get(&(collection_address, token_id)) + .ok_or(NftMarketplaceError::ThereIsNoSuchAuction)?; if auction.ended_at > exec::block_timestamp() { return Err(NftMarketplaceError::DeadlineError); @@ -184,7 +187,10 @@ impl NftMarketplace { collection_address: ActorId, token_id: u64, ) -> Result { - let auction = self.auctions.get(&(collection_address, token_id)).ok_or(NftMarketplaceError::ThereIsNoSuchAuction)?; + let auction = self + .auctions + .get(&(collection_address, token_id)) + .ok_or(NftMarketplaceError::ThereIsNoSuchAuction)?; if auction.owner != msg::source() { return Err(NftMarketplaceError::AccessDenied); diff --git a/src/lib.rs b/src/lib.rs index 495909b..57d9485 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -241,8 +241,10 @@ impl NftMarketplace { ) -> Result { let msg_src = msg::source(); - let (.., collection_owner) = self.collection_to_owner.get(&collection_address).ok_or(NftMarketplaceError::WrongCollectionAddress)?; - + let (.., collection_owner) = self + .collection_to_owner + .get(&collection_address) + .ok_or(NftMarketplaceError::WrongCollectionAddress)?; if self.admins.contains(&msg_src) { self.collection_to_owner.remove(&collection_address); @@ -371,7 +373,9 @@ impl NftMarketplace { &self, type_name: &str, ) -> Result<&TypeCollectionInfo, NftMarketplaceError> { - self.type_collections.get(type_name).ok_or(NftMarketplaceError::WrongCollectionName) + self.type_collections + .get(type_name) + .ok_or(NftMarketplaceError::WrongCollectionName) } } diff --git a/src/sale.rs b/src/sale.rs index 5bd9578..e5a8978 100644 --- a/src/sale.rs +++ b/src/sale.rs @@ -167,7 +167,10 @@ impl NftMarketplace { let payment = msg::value(); // check that such a sale exists and check the attached amount - let nft = self.sales.get(&(*collection_address, *token_id)).ok_or(NftMarketplaceError::SaleDoesNotExist)?; + let nft = self + .sales + .get(&(*collection_address, *token_id)) + .ok_or(NftMarketplaceError::SaleDoesNotExist)?; if payment < nft.price { msg::send(*buyer, "", payment).expect("Error in sending value"); return Err(NftMarketplaceError::ValueIsLessThanPrice); diff --git a/tests/test_marketplace.rs b/tests/test_marketplace.rs index 520adf0..a47df3a 100644 --- a/tests/test_marketplace.rs +++ b/tests/test_marketplace.rs @@ -419,7 +419,11 @@ fn sale_failures() { // low price let res = sale(&marketplace, USERS[1], address_nft, 0, 9_000_000_000_000); assert!(!res.main_failed()); - check_marketplace_error(USERS[1], &res, NftMarketplaceError::LessThanExistentialDeposit); + check_marketplace_error( + USERS[1], + &res, + NftMarketplaceError::LessThanExistentialDeposit, + ); // Only owner can send this action let price = 150_000_000_000_000; @@ -911,7 +915,11 @@ fn auction_failures() { duration_ms, ); assert!(!res.main_failed()); - check_marketplace_error(USERS[1], &res, NftMarketplaceError::LessThanExistentialDeposit); + check_marketplace_error( + USERS[1], + &res, + NftMarketplaceError::LessThanExistentialDeposit, + ); // Only token owner can send let res = create_auction( diff --git a/tests/test_nft.rs b/tests/test_nft.rs index 96338dd..4b5777d 100644 --- a/tests/test_nft.rs +++ b/tests/test_nft.rs @@ -184,7 +184,10 @@ fn successful_basics() { assert!(!res.main_failed()); let result = &res.decoded_log::>()[0]; println!("RES {:?}", result); - assert!(res.contains(&(USERS[3], Err::(NftError::ExhaustedLimit).encode()))); + assert!(res.contains(&( + USERS[3], + Err::(NftError::ExhaustedLimit).encode() + ))); // Successful Expand NFT in the collection let img_data = ImageData { @@ -740,8 +743,7 @@ fn permission_to_mint() { println!("Collection info: {:?}", state); } // Successful creation of a new collection - let init_nft_payload = - get_init_nft_payload(USERS[0].into(), 0, Some(3), 0, Some(vec![])); + let init_nft_payload = get_init_nft_payload(USERS[0].into(), 0, Some(3), 0, Some(vec![])); let res = create_collection( &marketplace, @@ -776,7 +778,7 @@ fn permission_to_mint() { let res = nft_collection.send(USERS[1], NftAction::Mint); assert!(!res.main_failed()); - // Fail mint NFT from user + // Fail mint NFT from user let res = nft_collection.send(USERS[1], NftAction::Mint); assert!(!res.main_failed()); diff --git a/tests/utils/mod.rs b/tests/utils/mod.rs index 2d7f954..612ec2a 100644 --- a/tests/utils/mod.rs +++ b/tests/utils/mod.rs @@ -1,7 +1,9 @@ use gstd::{ActorId, CodeId, Encode}; use gtest::{Program, RunResult, System}; use nft_io::{Config, ImageData, NftError, NftEvent, NftInit}; -use nft_marketplace_io::{NftMarketplaceAction, NftMarketplaceInit, NftMarketplaceError, NftMarketplaceEvent, Offer}; +use nft_marketplace_io::{ + NftMarketplaceAction, NftMarketplaceError, NftMarketplaceEvent, NftMarketplaceInit, Offer, +}; mod common; pub mod prelude; @@ -229,11 +231,14 @@ pub fn delete_collection( pub fn delete_admin(marketplace: &Program, admin: u64, user: ActorId) -> RunResult { marketplace.send(admin, NftMarketplaceAction::DeleteAdmin { user }) } -pub fn check_nft_error(from: u64, result: &RunResult, error: NftError){ +pub fn check_nft_error(from: u64, result: &RunResult, error: NftError) { assert!(result.contains(&(from, Err::(error).encode()))); } -pub fn check_marketplace_error(from: u64, result: &RunResult, error: NftMarketplaceError ){ - assert!(result.contains(&(from, Err::(error).encode()))); +pub fn check_marketplace_error(from: u64, result: &RunResult, error: NftMarketplaceError) { + assert!(result.contains(&( + from, + Err::(error).encode() + ))); } pub fn get_init_nft_payload( From fce115b33c3b8cc0e9a504497d8cb5bf503742b7 Mon Sep 17 00:00:00 2001 From: MedovTimur Date: Fri, 23 Feb 2024 14:49:17 +0300 Subject: [PATCH 4/8] update logic, add royalty, update tests --- Cargo.toml | 2 +- io/src/lib.rs | 61 +- nft/io/src/lib.rs | 18 +- nft/src/lib.rs | 55 +- src/auction.rs | 11 +- src/lib.rs | 168 +++++- src/nft_messages.rs | 58 +- src/offer.rs | 35 +- src/payment.rs | 14 +- src/sale.rs | 13 +- tests/auto_nft_node_test.rs | 244 ++++---- tests/composable_node_tests.rs | 120 ++-- tests/marketplace_node_test.rs | 1021 ++++++++------------------------ tests/test_composable_nft.rs | 464 +++++++-------- tests/test_marketplace.rs | 658 ++++++++++---------- tests/test_nft.rs | 289 ++++----- tests/utils/mod.rs | 711 +++++++++++++++------- tests/utils_gclient/mod.rs | 318 ++++++++++ 18 files changed, 2335 insertions(+), 1925 deletions(-) create mode 100644 tests/utils_gclient/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 33b64c5..362e507 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,6 @@ members = [ "nft", "composable-nft", "music-nft", - ] [package] @@ -38,6 +37,7 @@ primitive-types.workspace = true parity-scale-codec.workspace = true scale-info.workspace = true hex = "0.4.3" +serial_test = "*" [build-dependencies] diff --git a/io/src/lib.rs b/io/src/lib.rs index 45eac6c..dd0d48b 100644 --- a/io/src/lib.rs +++ b/io/src/lib.rs @@ -2,6 +2,8 @@ use gmeta::{In, InOut, Metadata}; use gstd::{prelude::*, ActorId, CodeId}; +pub const EXISTENTIAL_DEPOSIT: u128 = 10_000_000_000_000; + pub struct NftMarketplaceMetadata; impl Metadata for NftMarketplaceMetadata { type Init = In; @@ -18,7 +20,7 @@ impl Metadata for NftMarketplaceMetadata { /// (this action includes a transfer, so gas_for_close_auction must be greater than gas_for_transfer_token) /// * gas_for_delete_collection - gas that is needed to delete a collection /// (a message is sent to the collection contract to see if the collection can be deleted) -/// * gas_for_get_token_info - gas which is needed to get information from the collection about the token +/// * gas_for_get_info - gas which is needed to get information from the collection /// (used for sale, auction and offers) /// * time_between_create_collections - time between collection creation /// (to avoid regular users from creating collections too often) @@ -29,10 +31,14 @@ impl Metadata for NftMarketplaceMetadata { pub struct NftMarketplaceInit { pub gas_for_creation: u64, pub gas_for_transfer_token: u64, + pub gas_for_mint: u64, pub gas_for_close_auction: u64, pub gas_for_delete_collection: u64, - pub gas_for_get_token_info: u64, + pub gas_for_get_info: u64, pub time_between_create_collections: u64, + pub fee_per_uploaded_file: u128, + pub royalty_to_marketplace_for_trade: u16, + pub royalty_to_marketplace_for_mint: u16, pub minimum_transfer_value: u128, pub ms_in_block: u32, } @@ -49,6 +55,9 @@ pub enum NftMarketplaceAction { type_name: String, payload: Vec, }, + Mint { + collection_address: ActorId, + }, SaleNft { collection_address: ActorId, token_id: u64, @@ -66,7 +75,7 @@ pub enum NftMarketplaceAction { collection_address: ActorId, token_id: u64, min_price: u128, - duration_ms: u32, + duration: u32, }, AddBid { collection_address: ActorId, @@ -102,11 +111,15 @@ pub enum NftMarketplaceAction { }, UpdateConfig { gas_for_creation: Option, + gas_for_mint: Option, gas_for_transfer_token: Option, gas_for_close_auction: Option, gas_for_delete_collection: Option, - gas_for_get_token_info: Option, + gas_for_get_info: Option, time_between_create_collections: Option, + fee_per_uploaded_file: Option, + royalty_to_marketplace_for_trade: Option, + royalty_to_marketplace_for_mint: Option, minimum_transfer_value: Option, ms_in_block: Option, }, @@ -114,6 +127,13 @@ pub enum NftMarketplaceAction { #[derive(Encode, Decode, Debug, TypeInfo)] pub enum NftMarketplaceEvent { + Initialized { + time_between_create_collections: u64, + fee_per_uploaded_file: u128, + royalty_to_marketplace_for_trade: u16, + royalty_to_marketplace_for_mint: u16, + minimum_transfer_value: u128, + }, NewCollectionAdded { code_id: CodeId, meta_link: String, @@ -124,6 +144,10 @@ pub enum NftMarketplaceEvent { type_name: String, collection_address: ActorId, }, + Minted { + collection_address: ActorId, + minter: ActorId, + }, SaleNft { collection_address: ActorId, token_id: u64, @@ -144,7 +168,7 @@ pub enum NftMarketplaceEvent { collection_address: ActorId, token_id: u64, min_price: u128, - duration_ms: u32, + duration: u32, }, AuctionClosed { collection_address: ActorId, @@ -184,14 +208,21 @@ pub enum NftMarketplaceEvent { }, ConfigUpdated { gas_for_creation: Option, + gas_for_mint: Option, gas_for_transfer_token: Option, gas_for_close_auction: Option, gas_for_delete_collection: Option, - gas_for_get_token_info: Option, + gas_for_get_info: Option, time_between_create_collections: Option, + fee_per_uploaded_file: Option, + royalty_to_marketplace_for_trade: Option, + royalty_to_marketplace_for_mint: Option, minimum_transfer_value: Option, ms_in_block: Option, }, + BalanceHasBeenWithdrawn { + value: u128, + }, } #[derive(Debug, Clone, Encode, Decode, TypeInfo)] @@ -214,6 +245,10 @@ pub enum NftMarketplaceError { OfferDoesNotExist, SaleDoesNotExist, ValueIsLessThanPrice, + CreationError, + ErrorGetInfo, + WrongValue, + MintError, } #[derive(Encode, Decode, TypeInfo)] @@ -269,11 +304,15 @@ pub struct CollectionInfo { #[derive(Default, Debug, Encode, Decode, TypeInfo, Clone)] pub struct Config { pub gas_for_creation: u64, + pub gas_for_mint: u64, pub gas_for_transfer_token: u64, pub gas_for_close_auction: u64, pub gas_for_delete_collection: u64, - pub gas_for_get_token_info: u64, + pub gas_for_get_info: u64, pub time_between_create_collections: u64, + pub fee_per_uploaded_file: u128, + pub royalty_to_marketplace_for_trade: u16, + pub royalty_to_marketplace_for_mint: u16, pub minimum_transfer_value: u128, pub ms_in_block: u32, } @@ -317,6 +356,10 @@ pub enum NftAction { token_id: u64, }, CanDelete, + GetPaymentForMint, + Mint { + minter: ActorId, + }, } #[derive(Debug, Clone, Encode, Decode, TypeInfo)] @@ -334,6 +377,10 @@ pub enum NftEvent { royalty: u16, }, CanDelete(bool), + PaymentForMintReceived { + payment_for_mint: u128, + }, + SuccessfullyMinted, } #[derive(Debug, Clone, Encode, Decode, TypeInfo)] diff --git a/nft/io/src/lib.rs b/nft/io/src/lib.rs index 0784939..44a31a6 100644 --- a/nft/io/src/lib.rs +++ b/nft/io/src/lib.rs @@ -26,6 +26,7 @@ pub struct NftInit { pub config: Config, pub img_links_and_data: Vec<(String, ImageData)>, pub permission_to_mint: Option>, + pub fee_per_uploaded_file: u128, } #[derive(Debug, Clone, Encode, Decode, TypeInfo)] @@ -79,7 +80,10 @@ pub enum NftAction { token_id: NftId, }, CanDelete, - Mint, + GetPaymentForMint, + Mint { + minter: ActorId, + }, Approve { to: ActorId, token_id: NftId, @@ -125,15 +129,19 @@ pub enum NftEvent { royalty: u16, }, CanDelete(bool), - Initialized { - config: Config, - total_number_of_tokens: Option, - permission_to_mint: Option>, + PaymentForMintReceived { + payment_for_mint: u128, }, + SuccessfullyMinted, Minted { token_id: NftId, nft_data: Nft, }, + Initialized { + config: Config, + total_number_of_tokens: Option, + permission_to_mint: Option>, + }, Approved { to: ActorId, token_id: NftId, diff --git a/nft/src/lib.rs b/nft/src/lib.rs index 57e9653..89e6cd4 100644 --- a/nft/src/lib.rs +++ b/nft/src/lib.rs @@ -26,14 +26,14 @@ struct NftContract { static mut NFT_CONTRACT: Option = None; impl NftContract { - fn mint(&mut self, msg_src: ActorId, msg_value: u128) -> Result { + fn mint(&mut self, minter: ActorId, msg_value: u128) -> Result { // check if there are tokens for mint self.check_available_amount_of_tokens()?; // check if a user can make a mint: // - quantity limit // - user-specific limit - self.check_mint(&msg_src)?; + self.check_mint(&minter)?; let Some(next_nft_nonce) = self.nonce.checked_add(1) else { return Err(NftError::MathOverflow); @@ -84,7 +84,7 @@ impl NftContract { } self.owners - .entry(msg_src) + .entry(minter) .and_modify(|ids| { ids.insert(token_id); }) @@ -92,7 +92,7 @@ impl NftContract { let name = format!("{} - {}", self.config.name, token_id); let nft_data = Nft { - owner: msg_src, + owner: minter, name, description: self.config.description.clone(), metadata: vec![], @@ -102,13 +102,21 @@ impl NftContract { self.nonce = next_nft_nonce; self.tokens.insert(token_id, nft_data.clone()); self.restriction_mint - .entry(msg_src) + .entry(minter) .and_modify(|ids| { *ids += 1; }) .or_insert(1); - Ok(NftEvent::Minted { token_id, nft_data }) + msg::send_with_gas( + minter, + Ok::(NftEvent::Minted { token_id, nft_data }), + 0, + 0, + ) + .expect("Error in sending message"); + + Ok(NftEvent::SuccessfullyMinted) } fn transfer_from( &mut self, @@ -195,6 +203,11 @@ impl NftContract { royalty: self.config.royalty, }) } + fn get_payment_for_mint(&self) -> Result { + Ok(NftEvent::PaymentForMintReceived { + payment_for_mint: self.config.payment_for_mint, + }) + } fn expand(&mut self, additional_links: Vec<(String, ImageData)>) -> Result { let msg_src = msg::source(); self.check_collection_owner(msg_src)?; @@ -450,8 +463,18 @@ extern "C" fn init() { config, img_links_and_data, mut permission_to_mint, + fee_per_uploaded_file, } = msg::load().expect("Unable to decode `NftInit`."); + let msg_value = msg::value(); + let picture_fee = fee_per_uploaded_file * img_links_and_data.len() as u128; + + if picture_fee < EXISTENTIAL_DEPOSIT && msg_value != EXISTENTIAL_DEPOSIT + || picture_fee >= EXISTENTIAL_DEPOSIT && msg_value != picture_fee + { + panic!("Wrong value for picture fee"); + } + assert!( config .user_mint_limit @@ -522,13 +545,14 @@ extern "C" fn init() { 0, ) .expect("Error during send to owner `NftEvent::Initialized`"); + msg::reply( NftEvent::Initialized { config, total_number_of_tokens, permission_to_mint, }, - 0, + msg_value, ) .expect("Error during send reply `NftEvent::Initialized`"); } @@ -543,10 +567,10 @@ extern "C" fn handle() { }; let result = match action { - NftAction::Mint => { + NftAction::Mint { minter } => { let msg_source = msg::source(); let msg_value = msg::value(); - let result = nft_contract.mint(msg_source, msg_value); + let result = nft_contract.mint(minter, msg_value); if result.is_err() { msg::send_with_gas(msg_source, "", 0, msg_value).expect("Error in sending value"); } @@ -563,6 +587,7 @@ extern "C" fn handle() { NftAction::Expand { additional_links } => nft_contract.expand(additional_links), NftAction::ChangeConfig { config } => nft_contract.change_config(config), NftAction::GetTokenInfo { token_id } => nft_contract.get_token_info(token_id), + NftAction::GetPaymentForMint => nft_contract.get_payment_for_mint(), NftAction::CanDelete => nft_contract.can_delete(), NftAction::ChangeImg { token_id, img_link } => { nft_contract.change_image(token_id, img_link) @@ -614,16 +639,16 @@ impl From for NftState { } = value; let tokens = tokens - .iter() - .map(|(nft_id, nft)| (*nft_id, nft.clone())) + .into_iter() + .map(|(nft_id, nft)| (nft_id, nft)) .collect(); let owners = owners - .iter() - .map(|(actor_id, token_set)| (*actor_id, token_set.iter().copied().collect())) + .into_iter() + .map(|(actor_id, token_set)| (actor_id, token_set.into_iter().collect())) .collect(); let token_approvals = token_approvals - .iter() - .map(|(nft_id, actor_id)| (*nft_id, *actor_id)) + .into_iter() + .map(|(nft_id, actor_id)| (nft_id, actor_id)) .collect(); Self { diff --git a/src/auction.rs b/src/auction.rs index e34836b..1df90d3 100644 --- a/src/auction.rs +++ b/src/auction.rs @@ -10,7 +10,7 @@ impl NftMarketplace { collection_address: ActorId, token_id: u64, min_price: u128, - duration_ms: u32, + duration: u32, ) -> Result { if !self.collection_to_owner.contains_key(&collection_address) { return Err(NftMarketplaceError::WrongCollectionAddress); @@ -32,7 +32,7 @@ impl NftMarketplace { let (collection_owner, royalty) = check_token_info( &collection_address, token_id, - self.config.gas_for_get_token_info, + self.config.gas_for_get_info, &msg_src, &address_marketplace, ) @@ -56,7 +56,7 @@ impl NftMarketplace { .or_insert(Auction { owner: msg_src, started_at: exec::block_timestamp(), - ended_at: exec::block_timestamp() + duration_ms as u64, + ended_at: exec::block_timestamp() + (duration * self.config.ms_in_block) as u64, current_price: min_price, current_winner: ActorId::zero(), collection_owner, @@ -74,7 +74,7 @@ impl NftMarketplace { }, self.config.gas_for_close_auction, 0, - duration_ms / self.config.ms_in_block + 1, + duration, ) .expect("Error in sending delayed message"); @@ -82,7 +82,7 @@ impl NftMarketplace { collection_address, token_id, min_price, - duration_ms, + duration, }) } @@ -151,6 +151,7 @@ impl NftMarketplace { auction.owner, auction.current_price, auction.royalty, + self.config.royalty_to_marketplace_for_trade, self.config.minimum_transfer_value, ); } diff --git a/src/lib.rs b/src/lib.rs index 57d9485..08b5419 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ #![no_std] +use crate::nft_messages::*; use gstd::{ collections::HashMap, debug, exec, msg, prelude::*, prog::ProgramGenerator, ActorId, CodeId, }; @@ -35,10 +36,14 @@ extern "C" fn init() { let NftMarketplaceInit { gas_for_creation, gas_for_transfer_token, + gas_for_mint, gas_for_close_auction, gas_for_delete_collection, - gas_for_get_token_info, + gas_for_get_info, time_between_create_collections, + fee_per_uploaded_file, + royalty_to_marketplace_for_trade, + royalty_to_marketplace_for_mint, minimum_transfer_value, ms_in_block, } = msg::load().expect("Unable to decode `NftMarketplaceInit`"); @@ -48,16 +53,31 @@ extern "C" fn init() { config: Config { gas_for_creation, gas_for_transfer_token, + gas_for_mint, gas_for_close_auction, gas_for_delete_collection, - gas_for_get_token_info, + gas_for_get_info, time_between_create_collections, + fee_per_uploaded_file, + royalty_to_marketplace_for_trade, + royalty_to_marketplace_for_mint, minimum_transfer_value, ms_in_block, }, ..Default::default() }; unsafe { NFT_MARKETPLACE = Some(nft_marketplace) }; + msg::reply( + Ok::(NftMarketplaceEvent::Initialized { + time_between_create_collections, + fee_per_uploaded_file, + royalty_to_marketplace_for_trade, + royalty_to_marketplace_for_mint, + minimum_transfer_value, + }), + 0, + ) + .expect("Failed to encode or reply with `Result`."); } #[gstd::async_main] @@ -67,7 +87,7 @@ async fn main() { let nft_marketplace = unsafe { NFT_MARKETPLACE .as_mut() - .expect("`Collection Factory` is not initialized.") + .expect("`Nft Marketplace` is not initialized.") }; let result = match action { NftMarketplaceAction::AddNewCollection { @@ -79,6 +99,9 @@ async fn main() { NftMarketplaceAction::CreateCollection { type_name, payload } => { nft_marketplace.create_collection(type_name, payload).await } + NftMarketplaceAction::Mint { collection_address } => { + nft_marketplace.mint(collection_address).await + } NftMarketplaceAction::SaleNft { collection_address, token_id, @@ -99,15 +122,25 @@ async fn main() { NftMarketplaceAction::BuyNft { collection_address, token_id, - } => nft_marketplace.buy(collection_address, token_id).await, + } => { + let msg_source = msg::source(); + let msg_value = msg::value(); + let reply = nft_marketplace + .buy(collection_address, token_id, msg_source, msg_value) + .await; + if reply.is_err() { + msg::send_with_gas(msg_source, "", 0, msg_value).expect("Error in sending value"); + } + reply + } NftMarketplaceAction::CreateAuction { collection_address, token_id, min_price, - duration_ms, + duration, } => { nft_marketplace - .create_auction(collection_address, token_id, min_price, duration_ms) + .create_auction(collection_address, token_id, min_price, duration) .await } NftMarketplaceAction::AddBid { @@ -134,9 +167,16 @@ async fn main() { collection_address, token_id, } => { - nft_marketplace - .create_offer(collection_address, token_id) - .await + let msg_source = msg::source(); + let msg_value = msg::value(); + + let reply = nft_marketplace + .create_offer(collection_address, token_id, msg_source, msg_value) + .await; + if reply.is_err() { + msg::send_with_gas(msg_source, "", 0, msg_value).expect("Error in sending value"); + } + reply } NftMarketplaceAction::CancelOffer { collection_address, @@ -150,20 +190,28 @@ async fn main() { NftMarketplaceAction::DeleteAdmin { user } => nft_marketplace.delete_admin(user), NftMarketplaceAction::UpdateConfig { gas_for_creation, + gas_for_mint, gas_for_transfer_token, gas_for_close_auction, gas_for_delete_collection, - gas_for_get_token_info, + gas_for_get_info, time_between_create_collections, + royalty_to_marketplace_for_trade, + royalty_to_marketplace_for_mint, + fee_per_uploaded_file, minimum_transfer_value, ms_in_block, } => nft_marketplace.update_config( gas_for_creation, + gas_for_mint, gas_for_transfer_token, gas_for_close_auction, gas_for_delete_collection, - gas_for_get_token_info, + gas_for_get_info, time_between_create_collections, + fee_per_uploaded_file, + royalty_to_marketplace_for_trade, + royalty_to_marketplace_for_mint, minimum_transfer_value, ms_in_block, ), @@ -182,7 +230,8 @@ impl NftMarketplace { type_name: String, type_description: String, ) -> Result { - self.check_admin()?; + let msg_src = msg::source(); + self.check_admin(msg_src)?; let collection_info = TypeCollectionInfo { code_id, @@ -206,21 +255,30 @@ impl NftMarketplace { payload: Vec, ) -> Result { let msg_src = msg::source(); + let msg_value = msg::value(); self.check_time_creation(&msg_src)?; let collection_info = self.get_collection_info(&type_name)?; - debug!("PAYLOAD: {:?}", payload); - let (address, _) = ProgramGenerator::create_program_bytes_with_gas_for_reply( + let address = match ProgramGenerator::create_program_bytes_with_gas_for_reply( collection_info.code_id, payload, self.config.gas_for_creation, + msg_value, 0, - 0, - ) - .expect("Error during Collection program initialization") - .await - .expect("Program was not initialized"); + ) { + Ok(future) => match future.await { + Ok((address, _)) => address, + Err(_) => { + msg::send_with_gas(msg_src, "", 0, msg_value).expect("Error in sending value"); + return Err(NftMarketplaceError::CreationError); + } + }, + Err(_) => { + msg::send_with_gas(msg_src, "", 0, msg_value).expect("Error in sending value"); + return Err(NftMarketplaceError::CreationError); + } + }; self.collection_to_owner .insert(address, (type_name.clone(), msg_src)); @@ -235,6 +293,27 @@ impl NftMarketplace { }) } + pub async fn mint( + &mut self, + collection_address: ActorId, + ) -> Result { + let msg_src = msg::source(); + let msg_value = msg::value(); + + let reply = mint( + collection_address, + msg_src, + self.config.gas_for_mint, + self.config.gas_for_get_info, + self.config.royalty_to_marketplace_for_mint, + ) + .await; + if reply.is_err() { + msg::send_with_gas(msg_src, "", 0, msg_value).expect("Error in sending value"); + } + reply + } + pub async fn delete_collection( &mut self, collection_address: ActorId, @@ -280,7 +359,8 @@ impl NftMarketplace { &mut self, users: Vec, ) -> Result { - self.check_admin()?; + let msg_src = msg::source(); + self.check_admin(msg_src)?; self.admins.extend(users.clone()); Ok(NftMarketplaceEvent::AdminsAdded { users }) } @@ -288,25 +368,34 @@ impl NftMarketplace { &mut self, user: ActorId, ) -> Result { - self.check_admin()?; + let msg_src = msg::source(); + self.check_admin(msg_src)?; self.admins.retain(|&admin| admin != user); Ok(NftMarketplaceEvent::AdminDeleted { user }) } pub fn update_config( &mut self, gas_for_creation: Option, + gas_for_mint: Option, gas_for_transfer_token: Option, gas_for_close_auction: Option, gas_for_delete_collection: Option, - gas_for_get_token_info: Option, + gas_for_get_info: Option, time_between_create_collections: Option, + fee_per_uploaded_file: Option, + royalty_to_marketplace_for_trade: Option, + royalty_to_marketplace_for_mint: Option, minimum_transfer_value: Option, ms_in_block: Option, ) -> Result { - self.check_admin()?; + let msg_src = msg::source(); + self.check_admin(msg_src)?; if let Some(gas) = gas_for_creation { self.config.gas_for_creation = gas; } + if let Some(gas) = gas_for_mint { + self.config.gas_for_mint = gas; + } if let Some(gas) = gas_for_transfer_token { self.config.gas_for_transfer_token = gas; } @@ -316,12 +405,21 @@ impl NftMarketplace { if let Some(gas) = gas_for_delete_collection { self.config.gas_for_delete_collection = gas; } - if let Some(gas) = gas_for_get_token_info { - self.config.gas_for_get_token_info = gas; + if let Some(gas) = gas_for_get_info { + self.config.gas_for_get_info = gas; } if let Some(time) = time_between_create_collections { self.config.time_between_create_collections = time; } + if let Some(fee) = fee_per_uploaded_file { + self.config.fee_per_uploaded_file = fee; + } + if let Some(royalty) = royalty_to_marketplace_for_trade { + self.config.royalty_to_marketplace_for_trade = royalty; + } + if let Some(royalty) = royalty_to_marketplace_for_mint { + self.config.royalty_to_marketplace_for_mint = royalty; + } if let Some(min_value) = minimum_transfer_value { self.config.minimum_transfer_value = min_value; } @@ -331,16 +429,30 @@ impl NftMarketplace { Ok(NftMarketplaceEvent::ConfigUpdated { gas_for_creation, + gas_for_mint, gas_for_transfer_token, gas_for_close_auction, gas_for_delete_collection, - gas_for_get_token_info, + gas_for_get_info, time_between_create_collections, + fee_per_uploaded_file, + royalty_to_marketplace_for_trade, + royalty_to_marketplace_for_mint, minimum_transfer_value, ms_in_block, }) } + pub fn balance_out(&mut self, value: u128) -> Result { + let msg_src = msg::source(); + self.check_admin(msg_src)?; + if value < EXISTENTIAL_DEPOSIT { + return Err(NftMarketplaceError::LessThanExistentialDeposit); + } + msg::send_with_gas(msg_src, "", 0, value).expect("Error in sending value"); + Ok(NftMarketplaceEvent::BalanceHasBeenWithdrawn { value }) + } + fn check_time_creation(&self, user: &ActorId) -> Result<(), NftMarketplaceError> { if let Some(time) = self.time_creation.get(user) { if exec::block_timestamp() - time < self.config.time_between_create_collections @@ -352,8 +464,8 @@ impl NftMarketplace { Ok(()) } - fn check_admin(&self) -> Result<(), NftMarketplaceError> { - if !self.admins.contains(&msg::source()) { + fn check_admin(&self, msg_src: ActorId) -> Result<(), NftMarketplaceError> { + if !self.admins.contains(&msg_src) { return Err(NftMarketplaceError::AccessDenied); } Ok(()) diff --git a/src/nft_messages.rs b/src/nft_messages.rs index ff2aeef..2e7ad53 100644 --- a/src/nft_messages.rs +++ b/src/nft_messages.rs @@ -1,6 +1,60 @@ use gstd::{msg, prelude::*, ActorId}; use nft_marketplace_io::*; +pub async fn mint( + collection_address: ActorId, + minter: ActorId, + gas_for_mint: u64, + gas_for_get_info: u64, + royalty_to_marketplace: u16, +) -> Result { + let msg_value = msg::value(); + + let get_payment_for_mint_payload = NftAction::GetPaymentForMint; + + let reply = msg::send_with_gas_for_reply_as::>( + collection_address, + get_payment_for_mint_payload, + gas_for_get_info, + 0, + 0, + ) + .expect("Error during send message `NftAction::GetPaymentForMint`") + .await + .expect("Program was problem with GetPaymentForMint"); + + let reply = check_reply(reply)?; + + let payment_for_mint = match reply { + NftEvent::PaymentForMintReceived { payment_for_mint } => payment_for_mint, + _ => return Err(NftMarketplaceError::WrongReply), + }; + + let percent_to_marketplace = payment_for_mint * (royalty_to_marketplace as u128) / 10_000u128; + if percent_to_marketplace + payment_for_mint != msg_value { + return Err(NftMarketplaceError::WrongValue); + } + let mint_payload = NftAction::Mint { minter }; + + let reply = msg::send_with_gas_for_reply_as::>( + collection_address, + mint_payload, + gas_for_mint, + msg_value - percent_to_marketplace, + 0, + ) + .expect("Error during send message `NftAction::Mint`") + .await + .expect("Program was problem with mint"); + + check_reply(reply)?; + + Ok(NftMarketplaceEvent::Minted { + collection_address, + minter, + }) +} + pub async fn transfer_token( collection_address: ActorId, to: ActorId, @@ -47,7 +101,7 @@ pub async fn transfer_from_token( pub async fn check_token_info( collection_address: &ActorId, token_id: u64, - gas_for_get_token_info: u64, + gas_for_get_info: u64, msg_src: &ActorId, address_marketplace: &ActorId, ) -> Result<(ActorId, u16), NftMarketplaceError> { @@ -55,7 +109,7 @@ pub async fn check_token_info( let reply = msg::send_with_gas_for_reply_as::>( *collection_address, get_token_info_payload, - gas_for_get_token_info, + gas_for_get_info, 0, 0, ) diff --git a/src/offer.rs b/src/offer.rs index e47fa5e..68a36eb 100644 --- a/src/offer.rs +++ b/src/offer.rs @@ -9,24 +9,32 @@ impl NftMarketplace { &mut self, collection_address: ActorId, token_id: u64, + msg_src: ActorId, + msg_value: u128, ) -> Result { - let current_price = msg::value(); - if !self.collection_to_owner.contains_key(&collection_address) { return Err(NftMarketplaceError::WrongCollectionAddress); } let get_token_info_payload = NftAction::GetTokenInfo { token_id }; - let reply = msg::send_with_gas_for_reply_as::>( + + let reply = match msg::send_with_gas_for_reply_as::>( collection_address, get_token_info_payload, - self.config.gas_for_get_token_info, + self.config.gas_for_get_info, 0, 0, - ) - .expect("Error during `NftAction::GetTokenInfo`") - .await - .expect("Problem with get token info"); + ) { + Ok(future) => match future.await { + Ok(reply) => reply, + Err(_) => { + return Err(NftMarketplaceError::ErrorGetInfo); + } + }, + Err(_) => { + return Err(NftMarketplaceError::ErrorGetInfo); + } + }; match reply { Err(_) => Err(NftMarketplaceError::ErrorFromCollection), @@ -43,20 +51,20 @@ impl NftMarketplace { let offer = Offer { collection_address, token_id, - creator: msg::source(), + creator: msg_src, }; self.offers .entry(offer.clone()) .and_modify(|price| { msg::send_with_gas(offer.creator, "", 0, *price).expect("Error in sending value"); - *price = current_price; + *price = msg_value; }) - .or_insert(current_price); + .or_insert(msg_value); Ok(NftMarketplaceEvent::OfferCreated { collection_address, token_id, - price: current_price, + price: msg_value, }) } @@ -111,7 +119,7 @@ impl NftMarketplace { let (collection_owner, royalty) = check_token_info( &offer.collection_address, offer.token_id, - self.config.gas_for_get_token_info, + self.config.gas_for_get_info, &msg_src, &address_marketplace, ) @@ -134,6 +142,7 @@ impl NftMarketplace { msg_src, *price, royalty, + self.config.royalty_to_marketplace_for_trade, self.config.minimum_transfer_value, ); diff --git a/src/payment.rs b/src/payment.rs index 031b98b..2428e79 100644 --- a/src/payment.rs +++ b/src/payment.rs @@ -5,18 +5,26 @@ pub fn currency_transfer( token_owner: ActorId, price: u128, royalty: u16, + royalty_to_marketplace: u16, minimum_transfer_value: u128, ) { // calculate the percentage to the creator of the collection // current_price * royalty / 10_000 let percent_to_collection_creator = price * (royalty as u128) / 10_000u128; + let percent_to_marketplace = price * (royalty_to_marketplace as u128) / 10_000u128; if percent_to_collection_creator > minimum_transfer_value { // use send_with_gas to transfer the value directly to the balance, not to the mailbox. msg::send_with_gas(collection_owner, "", 0, percent_to_collection_creator) .expect("Error in sending value"); - msg::send_with_gas(token_owner, "", 0, price - percent_to_collection_creator) - .expect("Error in sending value"); + msg::send_with_gas( + token_owner, + "", + 0, + price - percent_to_collection_creator - percent_to_marketplace, + ) + .expect("Error in sending value"); } else { - msg::send_with_gas(token_owner, "", 0, price).expect("Error in sending value"); + msg::send_with_gas(token_owner, "", 0, price - percent_to_marketplace) + .expect("Error in sending value"); } } diff --git a/src/sale.rs b/src/sale.rs index e5a8978..4120b00 100644 --- a/src/sale.rs +++ b/src/sale.rs @@ -34,7 +34,7 @@ impl NftMarketplace { let (collection_owner, royalty) = check_token_info( &collection_address, token_id, - self.config.gas_for_get_token_info, + self.config.gas_for_get_info, &msg_src, &address_marketplace, ) @@ -119,9 +119,10 @@ impl NftMarketplace { &mut self, collection_address: ActorId, token_id: u64, + buyer: ActorId, + msg_value: u128, ) -> Result { - let buyer = msg::source(); - self.check_sale(&collection_address, &token_id, &buyer)?; + self.check_sale(&collection_address, &token_id, msg_value)?; // transfer the token to the buyer transfer_token( @@ -144,6 +145,7 @@ impl NftMarketplace { nft.token_owner, nft.price, nft.royalty, + self.config.royalty_to_marketplace_for_trade, self.config.minimum_transfer_value, ); // remove the sale from the marketplace @@ -162,17 +164,14 @@ impl NftMarketplace { &self, collection_address: &ActorId, token_id: &u64, - buyer: &ActorId, + payment: u128, ) -> Result<(), NftMarketplaceError> { - let payment = msg::value(); - // check that such a sale exists and check the attached amount let nft = self .sales .get(&(*collection_address, *token_id)) .ok_or(NftMarketplaceError::SaleDoesNotExist)?; if payment < nft.price { - msg::send(*buyer, "", payment).expect("Error in sending value"); return Err(NftMarketplaceError::ValueIsLessThanPrice); } diff --git a/tests/auto_nft_node_test.rs b/tests/auto_nft_node_test.rs index f5f9d61..3ad5278 100644 --- a/tests/auto_nft_node_test.rs +++ b/tests/auto_nft_node_test.rs @@ -1,122 +1,122 @@ -use auto_changed_nft_io::{ - Action, AutoNftAction, AutoNftInit, AutoNftState, Config, StateQuery, StateReply, -}; -use gclient::{EventProcessor, GearApi, Result}; -use gear_core::ids::ProgramId; -use gstd::Encode; - -#[tokio::test] -#[ignore] -async fn create_test() -> Result<()> { - let api = GearApi::dev_from_path("target/tmp/gear").await?; - - let mut listener = api.subscribe().await?; // Subscribing for events. - - // Checking that blocks still running. - assert!(listener.blocks_running().await?); - - let links: Vec = (0..4).map(|i| format!("Img-{}", i)).collect(); - let img_links: Vec<(Vec, u32)> = (0..10).map(|_| (links.clone(), 1 as u32)).collect(); - - // let time_for_change = 5; - let time_to_action = vec![(15, Action::ChangeImg), (30, Action::ChangeImg)]; - - let init_nft_payload = AutoNftInit { - owner: 100.into(), - config: Config { - name: "User Collection".to_string(), - description: "User Collection".to_string(), - collection_banner: "Collection banner".to_string(), - collection_tags: vec!["tag1".to_string()], - user_mint_limit: 3.into(), - // time_for_change, - time_to_action, - transferable: true, - approvable: true, - burnable: true, - sellable: true, - attendable: true, - }, - img_links, - } - .encode(); - - println!("!!!!!!"); - - let path = "target/wasm32-unknown-unknown/debug/auto_changed_nft.opt.wasm"; - - let gas_info = api - .calculate_upload_gas( - None, - gclient::code_from_os(path)?, - init_nft_payload.clone(), - 0, - true, - ) - .await?; - - println!("GAS_INFO MIN LIMIT: {:?}", gas_info.min_limit.clone()); - - let (message_id, program_id, _hash) = api - .upload_program_bytes( - gclient::code_from_os(path)?, - gclient::now_micros().to_le_bytes(), - init_nft_payload, - gas_info.min_limit, - 0, - ) - .await?; - - println!("!!!!!!"); - assert!(listener.message_processed(message_id).await?.succeed()); - assert!(listener.blocks_running().await?); - - let gas_info = api - .calculate_handle_gas(None, program_id, AutoNftAction::Mint.encode(), 0, true) - .await?; - - println!("GAS_INFO MIN LIMIT: {:?}", gas_info.min_limit.clone()); - let (message_id, _) = api - .send_message(program_id, AutoNftAction::Mint, 2 * gas_info.min_limit, 0) - .await?; - - assert!(listener.message_processed(message_id).await?.succeed()); - - let interval_sec: u64 = 15; - - let state = get_all_state_nft(&api, &program_id) - .await - .expect("Unexpected invalid state."); - - // println!("STATE: {:?}", state); - assert_eq!(state.tokens.get(0).unwrap().1.media_url.0, 0); - std::thread::sleep(std::time::Duration::from_secs(interval_sec.into())); - let state = get_all_state_nft(&api, &program_id) - .await - .expect("Unexpected invalid state."); - - // println!("STATE: {:?}", state); - assert_eq!(state.tokens.get(0).unwrap().1.media_url.0, 1); - - std::thread::sleep(std::time::Duration::from_secs(interval_sec.into())); - let state = get_all_state_nft(&api, &program_id) - .await - .expect("Unexpected invalid state."); - - // println!("STATE: {:?}", state); - assert_eq!(state.tokens.get(0).unwrap().1.media_url.0, 2); - - Ok(()) -} - -pub async fn get_all_state_nft(api: &GearApi, program_id: &ProgramId) -> Option { - let reply = api - .read_state(*program_id, StateQuery::All.encode()) - .await - .expect("Unexpected invalid reply."); - if let StateReply::All(state) = reply { - Some(state) - } else { - None - } -} +// use auto_changed_nft_io::{ +// Action, AutoNftAction, AutoNftInit, AutoNftState, Config, StateQuery, StateReply, +// }; +// use gclient::{EventProcessor, GearApi, Result}; +// use gear_core::ids::ProgramId; +// use gstd::Encode; + +// #[tokio::test] +// #[ignore] +// async fn create_test() -> Result<()> { +// let api = GearApi::dev_from_path("target/tmp/gear").await?; + +// let mut listener = api.subscribe().await?; // Subscribing for events. + +// // Checking that blocks still running. +// assert!(listener.blocks_running().await?); + +// let links: Vec = (0..4).map(|i| format!("Img-{}", i)).collect(); +// let img_links: Vec<(Vec, u32)> = (0..10).map(|_| (links.clone(), 1 as u32)).collect(); + +// // let time_for_change = 5; +// let time_to_action = vec![(15, Action::ChangeImg), (30, Action::ChangeImg)]; + +// let init_nft_payload = AutoNftInit { +// owner: 100.into(), +// config: Config { +// name: "User Collection".to_string(), +// description: "User Collection".to_string(), +// collection_banner: "Collection banner".to_string(), +// collection_tags: vec!["tag1".to_string()], +// user_mint_limit: 3.into(), +// // time_for_change, +// time_to_action, +// transferable: true, +// approvable: true, +// burnable: true, +// sellable: true, +// attendable: true, +// }, +// img_links, +// } +// .encode(); + +// println!("!!!!!!"); + +// let path = "target/wasm32-unknown-unknown/debug/auto_changed_nft.opt.wasm"; + +// let gas_info = api +// .calculate_upload_gas( +// None, +// gclient::code_from_os(path)?, +// init_nft_payload.clone(), +// 0, +// true, +// ) +// .await?; + +// println!("GAS_INFO MIN LIMIT: {:?}", gas_info.min_limit.clone()); + +// let (message_id, program_id, _hash) = api +// .upload_program_bytes( +// gclient::code_from_os(path)?, +// gclient::now_micros().to_le_bytes(), +// init_nft_payload, +// gas_info.min_limit, +// 0, +// ) +// .await?; + +// println!("!!!!!!"); +// assert!(listener.message_processed(message_id).await?.succeed()); +// assert!(listener.blocks_running().await?); + +// let gas_info = api +// .calculate_handle_gas(None, program_id, AutoNftAction::Mint.encode(), 0, true) +// .await?; + +// println!("GAS_INFO MIN LIMIT: {:?}", gas_info.min_limit.clone()); +// let (message_id, _) = api +// .send_message(program_id, AutoNftAction::Mint, 2 * gas_info.min_limit, 0) +// .await?; + +// assert!(listener.message_processed(message_id).await?.succeed()); + +// let interval_sec: u64 = 15; + +// let state = get_all_state_nft(&api, &program_id) +// .await +// .expect("Unexpected invalid state."); + +// // println!("STATE: {:?}", state); +// assert_eq!(state.tokens.get(0).unwrap().1.media_url.0, 0); +// std::thread::sleep(std::time::Duration::from_secs(interval_sec.into())); +// let state = get_all_state_nft(&api, &program_id) +// .await +// .expect("Unexpected invalid state."); + +// // println!("STATE: {:?}", state); +// assert_eq!(state.tokens.get(0).unwrap().1.media_url.0, 1); + +// std::thread::sleep(std::time::Duration::from_secs(interval_sec.into())); +// let state = get_all_state_nft(&api, &program_id) +// .await +// .expect("Unexpected invalid state."); + +// // println!("STATE: {:?}", state); +// assert_eq!(state.tokens.get(0).unwrap().1.media_url.0, 2); + +// Ok(()) +// } + +// pub async fn get_all_state_nft(api: &GearApi, program_id: &ProgramId) -> Option { +// let reply = api +// .read_state(*program_id, StateQuery::All.encode()) +// .await +// .expect("Unexpected invalid reply."); +// if let StateReply::All(state) = reply { +// Some(state) +// } else { +// None +// } +// } diff --git a/tests/composable_node_tests.rs b/tests/composable_node_tests.rs index bcf76f5..98fbde6 100644 --- a/tests/composable_node_tests.rs +++ b/tests/composable_node_tests.rs @@ -1,74 +1,74 @@ -use composable_nft_io::{ComposableNftInit, Config as ComposableConfig}; -use gclient::{EventProcessor, GearApi, Result}; -use gstd::Encode; +// use composable_nft_io::{ComposableNftInit, Config as ComposableConfig}; +// use gclient::{EventProcessor, GearApi, Result}; +// use gstd::Encode; -#[tokio::test] -#[ignore] -async fn create_test() -> Result<()> { - let api = GearApi::dev_from_path("target/tmp/gear").await?; +// #[tokio::test] +// #[ignore] +// async fn create_test() -> Result<()> { +// let api = GearApi::dev_from_path("target/tmp/gear").await?; - let mut listener = api.subscribe().await?; // Subscribing for events. +// let mut listener = api.subscribe().await?; // Subscribing for events. - // Checking that blocks still running. - assert!(listener.blocks_running().await?); +// // Checking that blocks still running. +// assert!(listener.blocks_running().await?); - let mut img_links: Vec> = Vec::with_capacity(4); +// let mut img_links: Vec> = Vec::with_capacity(4); - for k in 0..10 { - let inner_vec: Vec = (0..10).map(|i| format!("Value{}-{}", k, i)).collect(); +// for k in 0..10 { +// let inner_vec: Vec = (0..10).map(|i| format!("Value{}-{}", k, i)).collect(); - img_links.push(inner_vec); - } +// img_links.push(inner_vec); +// } - let init_nft_payload = ComposableNftInit { - owner: 100.into(), - config: ComposableConfig { - name: "User Collection".to_string(), - description: "User Collection".to_string(), - collection_banner: "Collection banner".to_string(), - collection_logo: "Collection logo".to_string(), - collection_tags: vec!["tag1".to_string()], - additional_links: None, - royalty: 0, - payment_for_mint: 0, - user_mint_limit: 3.into(), - tokens_limit: Some(500), - transferable: Some(0), - sellable: Some(0), - }, - img_links, - } - .encode(); +// let init_nft_payload = ComposableNftInit { +// owner: 100.into(), +// config: ComposableConfig { +// name: "User Collection".to_string(), +// description: "User Collection".to_string(), +// collection_banner: "Collection banner".to_string(), +// collection_logo: "Collection logo".to_string(), +// collection_tags: vec!["tag1".to_string()], +// additional_links: None, +// royalty: 0, +// payment_for_mint: 0, +// user_mint_limit: 3.into(), +// tokens_limit: Some(500), +// transferable: Some(0), +// sellable: Some(0), +// }, +// img_links, +// } +// .encode(); - println!("!!!!!!"); +// println!("!!!!!!"); - let path = "target/wasm32-unknown-unknown/debug/composable_nft.opt.wasm"; +// let path = "target/wasm32-unknown-unknown/debug/composable_nft.opt.wasm"; - let gas_info = api - .calculate_upload_gas( - None, - gclient::code_from_os(path)?, - init_nft_payload.clone(), - 0, - true, - ) - .await?; +// let gas_info = api +// .calculate_upload_gas( +// None, +// gclient::code_from_os(path)?, +// init_nft_payload.clone(), +// 0, +// true, +// ) +// .await?; - println!("GAS_INFO MIN LIMIT: {:?}", gas_info.min_limit.clone()); +// println!("GAS_INFO MIN LIMIT: {:?}", gas_info.min_limit.clone()); - let (message_id, program_id, _hash) = api - .upload_program_bytes( - gclient::code_from_os(path)?, - gclient::now_micros().to_le_bytes(), - init_nft_payload, - gas_info.min_limit, - 0, - ) - .await?; +// let (message_id, program_id, _hash) = api +// .upload_program_bytes( +// gclient::code_from_os(path)?, +// gclient::now_micros().to_le_bytes(), +// init_nft_payload, +// gas_info.min_limit, +// 0, +// ) +// .await?; - println!("!!!!!!"); - assert!(listener.message_processed(message_id).await?.succeed()); - assert!(listener.blocks_running().await?); +// println!("!!!!!!"); +// assert!(listener.message_processed(message_id).await?.succeed()); +// assert!(listener.blocks_running().await?); - Ok(()) -} +// Ok(()) +// } diff --git a/tests/marketplace_node_test.rs b/tests/marketplace_node_test.rs index d6141ca..a91fbb1 100644 --- a/tests/marketplace_node_test.rs +++ b/tests/marketplace_node_test.rs @@ -1,45 +1,11 @@ -use gclient::{ - errors::{Gear, ModuleError}, - Error as GclientError, EventListener, EventProcessor, GearApi, Result, -}; +use gclient::{EventProcessor, GearApi, Result}; use gear_core::ids::ProgramId; -use gstd::{prelude::*, ActorId}; -use nft_io::{ - Config, ImageData, NftAction, NftInit, NftState, StateQuery as StateQueryNft, - StateReply as StateReplyNft, -}; +use gstd::prelude::*; use nft_marketplace_io::*; - -const USERS: &[u64] = &[5, 6, 7, 8]; -pub const USERS_STR: &[&str] = &["//John", "//Mike", "//Dan"]; -const ALICE: [u8; 32] = [ - 212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, - 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125, -]; - -pub trait ApiUtils { - fn get_actor_id(&self) -> ActorId; - fn get_specific_actor_id(&self, value: impl AsRef) -> ActorId; -} - -impl ApiUtils for GearApi { - fn get_actor_id(&self) -> ActorId { - ActorId::new( - self.account_id() - .encode() - .try_into() - .expect("Unexpected invalid account id length."), - ) - } - - fn get_specific_actor_id(&self, value: impl AsRef) -> ActorId { - let api_temp = self - .clone() - .with(value) - .expect("Unable to build `GearApi` instance with provided signer."); - api_temp.get_actor_id() - } -} +use std::time::Instant; +mod utils_gclient; +use serial_test::serial; +use utils_gclient::*; #[tokio::test] #[ignore] @@ -47,152 +13,62 @@ async fn create_test() -> Result<()> { let api = GearApi::dev_from_path("target/tmp/gear").await?; let mut listener = api.subscribe().await?; // Subscribing for events. - - // Checking that blocks still running. assert!(listener.blocks_running().await?); - let init_marketplace = NftMarketplaceInit { - gas_for_creation: 200_000_000_000, - gas_for_transfer_token: 5_000_000_000, - gas_for_close_auction: 10_000_000_000, - gas_for_delete_collection: 5_000_000_000, - gas_for_get_token_info: 5_000_000_000, - time_between_create_collections: 3_600_000, // 1 hour in milliseconds - minimum_transfer_value: 10_000_000_000_000, - ms_in_block: 3_000, - } - .encode(); - - let path = "target/wasm32-unknown-unknown/debug/nft_marketplace.opt.wasm"; - - let gas_info = api - .calculate_upload_gas( - None, - gclient::code_from_os(path)?, - init_marketplace.clone(), - 0, - true, - ) - .await?; - - let (message_id, program_id, _hash) = api - .upload_program_bytes( - gclient::code_from_os(path)?, - gclient::now_micros().to_le_bytes(), - init_marketplace, - gas_info.min_limit, - 0, - ) - .await?; + let royalty_to_marketplace = 200; + let (message_id, program_id) = init_marketplace(&api) + .await + .expect("Error init marketplace"); assert!(listener.message_processed(message_id).await?.succeed()); - let (nft_code_id, _) = api - .upload_code_by_path("target/wasm32-unknown-unknown/debug/nft.opt.wasm") - .await?; - - let nft_code_id: [u8; 32] = nft_code_id.into(); - - let add_collection_payload = NftMarketplaceAction::AddNewCollection { - code_id: nft_code_id.into(), - meta_link: String::from("My Meta"), - type_name: String::from("Simple NFT"), - type_description: String::from("My Collection"), - }; - - let gas_info = api - .calculate_handle_gas(None, program_id, add_collection_payload.encode(), 0, true) - .await?; - - let (message_id, _) = api - .send_message(program_id, add_collection_payload, gas_info.min_limit, 0) - .await?; - + let message_id = add_new_collection(&api, program_id) + .await + .expect("Error add new collection"); assert!(listener.message_processed(message_id).await?.succeed()); - let img_data = ImageData { - limit_copies: Some(1), - auto_changing_rules: None, - }; - let img_links_and_data: Vec<(String, ImageData)> = (0..10) - .map(|i| (format!("Img-{}", i), img_data.clone())) - .collect(); - - // Successful creation of a new collection - let init_nft_payload = NftInit { - collection_owner: USERS[0].into(), - config: Config { - name: "User Collection".to_string(), - description: "User Collection".to_string(), - collection_banner: "Collection banner".to_string(), - collection_logo: "Collection logo".to_string(), - collection_tags: vec!["tag1".to_string()], - additional_links: None, - royalty: 0, - user_mint_limit: Some(3), - payment_for_mint: 0, - transferable: Some(0), - sellable: Some(0), - }, - img_links_and_data, - permission_to_mint: None, - } - .encode(); - - let create_collection_payload = NftMarketplaceAction::CreateCollection { - type_name: String::from("Simple NFT"), - payload: init_nft_payload, - }; - let gas_info = api - .calculate_handle_gas( - None, - program_id, - create_collection_payload.encode(), - 0, - true, - ) - .await?; - - println!("GAS_INFO MIN LIMIT: {:?}", gas_info.min_limit.clone()); - let (message_id, _) = api - .send_message(program_id, create_collection_payload, gas_info.min_limit, 0) - .await?; - + let message_id = create_collection(&api, program_id) + .await + .expect("Error create collection"); assert!(listener.message_processed(message_id).await?.succeed()); - assert!(listener.blocks_running().await?); + // Check marketplace balance + let marketplace_balance = api.total_balance(program_id).await?; + assert_eq!(marketplace_balance, 10_000_000_000_000, "Wrong value"); + + // Check that collection created let state = get_all_collection_state(&api, &program_id) .await .expect("Unexpected invalid state."); - assert!(!state.is_empty(), "Collections shouldn't be empty"); - println!("Collections: {:?}", state); - let address_nft = state[0].0; - let address_nft = address_nft.as_ref(); - //let address_nft = hex::decode(address_nft).unwrap(); - //let nft_pid = ProgramId::decode(&mut address_nft).unwrap(); - let nft_pid: ProgramId = address_nft.into(); + // get the address and ProgramId of the created collection + let address_nft = state[0].0; + let address_nft_list = address_nft.as_ref(); + let nft_pid: ProgramId = address_nft_list.into(); + // Check collection state let state = get_all_state_nft(&api, &nft_pid) .await .expect("Unexpected invalid state."); - assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); - println!("NFT Collection STATE: {:?}", state); - - let gas_info = api - .calculate_handle_gas(None, nft_pid, NftAction::Mint.encode(), 0, true) - .await?; - - println!("GAS_INFO MIN LIMIT: {:?}", gas_info.min_limit.clone()); - let (message_id, _) = api - .send_message(nft_pid, NftAction::Mint, gas_info.min_limit * 2, 0) - .await?; + let percent_to_marketplace = 10_000_000_000_000 * royalty_to_marketplace as u128 / 10_000; + let payment_for_mint = 10_000_000_000_000 + percent_to_marketplace; + let message_id = mint(&api, program_id, address_nft, payment_for_mint) + .await + .expect("Error mint"); assert!(listener.message_processed(message_id).await?.succeed()); - assert!(listener.blocks_running().await?); + // Check marketplace balance + let marketplace_balance_after_mint = api.total_balance(program_id).await?; + assert_eq!( + marketplace_balance_after_mint, + marketplace_balance + percent_to_marketplace, + "Wrong value" + ); + + // Check success of mint let state = get_all_state_nft(&api, &nft_pid) .await .expect("Unexpected invalid state."); @@ -204,253 +80,130 @@ async fn create_test() -> Result<()> { } #[tokio::test] +#[serial] #[ignore] async fn sale_test() -> Result<()> { let api = GearApi::dev_from_path("target/tmp/gear").await?; let mut listener = api.subscribe().await?; // Subscribing for events. - - // Checking that blocks still running. assert!(listener.blocks_running().await?); - let init_marketplace = NftMarketplaceInit { - gas_for_creation: 200_000_000_000, - gas_for_transfer_token: 5_000_000_000, - gas_for_close_auction: 10_000_000_000, - gas_for_delete_collection: 5_000_000_000, - gas_for_get_token_info: 5_000_000_000, - time_between_create_collections: 3_600_000, // 1 hour in milliseconds - minimum_transfer_value: 10_000_000_000_000, - ms_in_block: 3_000, - } - .encode(); - - let path = "target/wasm32-unknown-unknown/debug/nft_marketplace.opt.wasm"; - - let gas_info = api - .calculate_upload_gas( - None, - gclient::code_from_os(path)?, - init_marketplace.clone(), - 0, - true, - ) - .await?; - - let (message_id, program_id, _hash) = api - .upload_program_bytes( - gclient::code_from_os(path)?, - gclient::now_micros().to_le_bytes(), - init_marketplace, - gas_info.min_limit, - 0, - ) - .await?; + let royalty_to_marketplace = 200; + let (message_id, program_id) = init_marketplace(&api) + .await + .expect("Error init marketplace"); assert!(listener.message_processed(message_id).await?.succeed()); - let (nft_code_id, _) = api - .upload_code_by_path("target/wasm32-unknown-unknown/debug/nft.opt.wasm") - .await?; - - let nft_code_id: [u8; 32] = nft_code_id.into(); - - let add_collection_payload = NftMarketplaceAction::AddNewCollection { - code_id: nft_code_id.into(), - meta_link: String::from("My Meta"), - type_name: String::from("Simple NFT"), - type_description: String::from("My Collection"), - }; - - let gas_info = api - .calculate_handle_gas(None, program_id, add_collection_payload.encode(), 0, true) - .await?; - - let (message_id, _) = api - .send_message(program_id, add_collection_payload, gas_info.min_limit, 0) - .await?; - + let message_id = add_new_collection(&api, program_id) + .await + .expect("Error add new collection"); assert!(listener.message_processed(message_id).await?.succeed()); - let img_data = ImageData { - limit_copies: Some(1), - auto_changing_rules: None, - }; - let img_links_and_data: Vec<(String, ImageData)> = (0..10) - .map(|i| (format!("Img-{}", i), img_data.clone())) - .collect(); - - // Successful creation of a new collection - let init_nft_payload = NftInit { - collection_owner: ALICE.into(), - config: Config { - name: "User Collection".to_string(), - description: "User Collection".to_string(), - collection_banner: "Collection banner".to_string(), - collection_logo: "Collection logo".to_string(), - collection_tags: vec!["tag1".to_string()], - additional_links: None, - royalty: 1_000, - user_mint_limit: Some(3), - payment_for_mint: 0, - transferable: Some(0), - sellable: Some(0), - }, - img_links_and_data, - permission_to_mint: None, - } - .encode(); - - let create_collection_payload = NftMarketplaceAction::CreateCollection { - type_name: String::from("Simple NFT"), - payload: init_nft_payload, - }; - let gas_info = api - .calculate_handle_gas( - None, - program_id, - create_collection_payload.encode(), - 0, - true, - ) - .await?; - - println!("GAS_INFO MIN LIMIT: {:?}", gas_info.min_limit.clone()); - let (message_id, _) = api - .send_message(program_id, create_collection_payload, gas_info.min_limit, 0) - .await?; - + let message_id = create_collection(&api, program_id) + .await + .expect("Error create collection"); assert!(listener.message_processed(message_id).await?.succeed()); - assert!(listener.blocks_running().await?); + // Check marketplace balance + let marketplace_balance = api.total_balance(program_id).await?; + assert_eq!(marketplace_balance, 10_000_000_000_000, "Wrong value"); + + // Check that collection created let state = get_all_collection_state(&api, &program_id) .await .expect("Unexpected invalid state."); - assert!(!state.is_empty(), "Collections shouldn't be empty"); - println!("Collections: {:?}", state); - let address_nft = state[0].0; + // get the address and ProgramId of the created collection + let address_nft = state[0].0; let address = address_nft.as_ref(); - //let address_nft = hex::decode(address_nft).unwrap(); - //let nft_pid = ProgramId::decode(&mut address_nft).unwrap(); let nft_pid: ProgramId = address.into(); + // Check collection state let state = get_all_state_nft(&api, &nft_pid) .await .expect("Unexpected invalid state."); + assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); - assert_eq!(state.collection_owner, ALICE.into(), "Wrong Admin"); - println!("NFT Collection STATE: {:?}", state); - - let gas_info = api - .calculate_handle_gas(None, nft_pid, NftAction::Mint.encode(), 0, true) - .await?; - - println!("GAS_INFO MIN LIMIT: {:?}", gas_info.min_limit.clone()); - let (message_id, _) = api - .send_message(nft_pid, NftAction::Mint, gas_info.min_limit * 2, 0) - .await?; - + let percent_to_marketplace = 10_000_000_000_000 * royalty_to_marketplace as u128 / 10_000; + let payment_for_mint = 10_000_000_000_000 + percent_to_marketplace; + let message_id = mint(&api, program_id, address_nft, payment_for_mint) + .await + .expect("Error mint"); assert!(listener.message_processed(message_id).await?.succeed()); - assert!(listener.blocks_running().await?); + // Check marketplace balance + let marketplace_balance_after_mint = api.total_balance(program_id).await?; + assert_eq!( + marketplace_balance_after_mint, + marketplace_balance + percent_to_marketplace, + "Wrong value" + ); + + // Check success of mint let state = get_all_state_nft(&api, &nft_pid) .await .expect("Unexpected invalid state."); - assert!(!state.tokens.is_empty()); assert_eq!(state.img_links_and_data.len(), 9); - let address_marketplace: ActorId = program_id.into_bytes().into(); - let gas_info = api - .calculate_handle_gas( - None, - nft_pid, - NftAction::Approve { - to: address_marketplace, - token_id: 0, - } - .encode(), - 0, - true, - ) - .await?; - - println!("GAS_INFO MIN LIMIT: {:?}", gas_info.min_limit.clone()); - let (message_id, _) = api - .send_message( - nft_pid, - NftAction::Approve { - to: address_marketplace, - token_id: 0, - }, - gas_info.min_limit * 2, - 0, - ) - .await?; - + // Approve to marketplace + let message_id = approve(&api, program_id, nft_pid) + .await + .expect("Error approve"); assert!(listener.message_processed(message_id).await?.succeed()); - assert!(listener.blocks_running().await?); + let price = 150_000_000_000_000; + let percent_to_marketplace = price * royalty_to_marketplace as u128 / 10_000; + + // Sale let sale_payload = NftMarketplaceAction::SaleNft { collection_address: address_nft, token_id: 0, - price: 150_000_000_000_000, + price, }; - let gas_info = api .calculate_handle_gas(None, program_id, sale_payload.encode(), 0, true) .await?; - - println!("GAS_INFO MIN LIMIT: {:?}", gas_info.min_limit.clone()); let (message_id, _) = api .send_message(program_id, sale_payload, gas_info.min_limit, 0) .await?; assert!(listener.message_processed(message_id).await?.succeed()); - assert!(listener.blocks_running().await?); - - let state = get_marketplace_state(&api, &program_id) - .await - .expect("Unexpected invalid state."); - println!("\nSTATE: {:?}", state); - - let alice_balance = api.total_balance(api.account_id()).await?; - let amount = alice_balance / 10; - println!("AMOUNT: {:?}", amount.clone()); - api.transfer( - api.get_specific_actor_id(USERS_STR[0]) - .encode() - .as_slice() - .try_into() - .expect("Unexpected invalid `ProgramId`."), - amount, - ) - .await?; - - let client = api - .clone() - .with(USERS_STR[0]) - .expect("Unable to change signer."); + let client = get_new_client(&api, USERS_STR[0]).await; let mut client_listener = client.subscribe().await?; + + // Buy let buy_payload = NftMarketplaceAction::BuyNft { collection_address: address_nft, token_id: 0, }; let gas_info = api - .calculate_handle_gas(None, program_id, buy_payload.encode(), 0, true) + .calculate_handle_gas( + None, + program_id, + buy_payload.encode(), + 150_000_000_000_000, + true, + ) .await?; - println!("GAS_INFO MIN LIMIT: {:?}", gas_info.min_limit.clone()); let (message_id, _) = client - .send_message(program_id, buy_payload, 10_000_000_000, 150_000_000_000_000) + .send_message( + program_id, + buy_payload, + gas_info.min_limit, + 150_000_000_000_000, + ) .await?; - println!("!!!!!!!!!!!!!!!!!!!!"); - // assert!(client_listener.message_processed(message_id).await?.succeed()); - // assert!(client_listener.blocks_running().await?); + assert!(client_listener + .message_processed(message_id) + .await? + .succeed()); + // Check success of sale let state = get_all_state_nft(&api, &nft_pid) .await .expect("Unexpected invalid state."); @@ -459,6 +212,14 @@ async fn sale_test() -> Result<()> { assert_eq!(token.0, 0); assert_eq!(token.1.owner, api.get_specific_actor_id(USERS_STR[0])); + // check balance of marketplace + let marketplace_balance_after_sale = api.total_balance(program_id).await?; + assert_eq!( + marketplace_balance_after_sale, + marketplace_balance_after_mint + percent_to_marketplace, + "Wrong value" + ); + Ok(()) } @@ -468,150 +229,62 @@ async fn auction_test() -> Result<()> { let api = GearApi::dev_from_path("target/tmp/gear").await?; let mut listener = api.subscribe().await?; // Subscribing for events. - - // Checking that blocks still running. assert!(listener.blocks_running().await?); - let init_marketplace = NftMarketplaceInit { - gas_for_creation: 200_000_000_000, - gas_for_transfer_token: 5_000_000_000, - gas_for_close_auction: 10_000_000_000, - gas_for_delete_collection: 5_000_000_000, - gas_for_get_token_info: 5_000_000_000, - time_between_create_collections: 3_600_000, // 1 hour in milliseconds - minimum_transfer_value: 10_000_000_000_000, - ms_in_block: 3_000, - } - .encode(); - - let path = "target/wasm32-unknown-unknown/debug/nft_marketplace.opt.wasm"; - - let gas_info = api - .calculate_upload_gas( - None, - gclient::code_from_os(path)?, - init_marketplace.clone(), - 0, - true, - ) - .await?; - - let (message_id, program_id, _hash) = api - .upload_program_bytes( - gclient::code_from_os(path)?, - gclient::now_micros().to_le_bytes(), - init_marketplace, - gas_info.min_limit, - 0, - ) - .await?; + let royalty_to_marketplace = 200; + let (message_id, program_id) = init_marketplace(&api) + .await + .expect("Error init marketplace"); assert!(listener.message_processed(message_id).await?.succeed()); - let (nft_code_id, _) = api - .upload_code_by_path("target/wasm32-unknown-unknown/debug/nft.opt.wasm") - .await?; - - let nft_code_id: [u8; 32] = nft_code_id.into(); - - let add_collection_payload = NftMarketplaceAction::AddNewCollection { - code_id: nft_code_id.into(), - meta_link: String::from("My Meta"), - type_name: String::from("Simple NFT"), - type_description: String::from("My Collection"), - }; - - let gas_info = api - .calculate_handle_gas(None, program_id, add_collection_payload.encode(), 0, true) - .await?; - - let (message_id, _) = api - .send_message(program_id, add_collection_payload, gas_info.min_limit, 0) - .await?; - + let message_id = add_new_collection(&api, program_id) + .await + .expect("Error add new collection"); assert!(listener.message_processed(message_id).await?.succeed()); - let img_data = ImageData { - limit_copies: Some(1), - auto_changing_rules: None, - }; - let img_links_and_data: Vec<(String, ImageData)> = (0..10) - .map(|i| (format!("Img-{}", i), img_data.clone())) - .collect(); - - // Successful creation of a new collection - let init_nft_payload = NftInit { - collection_owner: ALICE.into(), - config: Config { - name: "User Collection".to_string(), - description: "User Collection".to_string(), - collection_banner: "Collection banner".to_string(), - collection_logo: "Collection logo".to_string(), - collection_tags: vec!["tag1".to_string()], - additional_links: None, - royalty: 1_000, - user_mint_limit: Some(3), - payment_for_mint: 0, - transferable: Some(0), - sellable: Some(0), - }, - img_links_and_data, - permission_to_mint: None, - } - .encode(); - - let create_collection_payload = NftMarketplaceAction::CreateCollection { - type_name: String::from("Simple NFT"), - payload: init_nft_payload, - }; - let gas_info = api - .calculate_handle_gas( - None, - program_id, - create_collection_payload.encode(), - 0, - true, - ) - .await?; - - println!("GAS_INFO MIN LIMIT: {:?}", gas_info.min_limit.clone()); - let (message_id, _) = api - .send_message(program_id, create_collection_payload, gas_info.min_limit, 0) - .await?; - + let message_id = create_collection(&api, program_id) + .await + .expect("Error create collection"); assert!(listener.message_processed(message_id).await?.succeed()); - assert!(listener.blocks_running().await?); + // Check marketplace balance + let marketplace_balance = api.total_balance(program_id).await?; + assert_eq!(marketplace_balance, 10_000_000_000_000, "Wrong value"); + + // Check that collection created let state = get_all_collection_state(&api, &program_id) .await .expect("Unexpected invalid state."); - assert!(!state.is_empty(), "Collections shouldn't be empty"); - println!("Collections: {:?}", state); - let address_nft = state[0].0; - let address = address_nft.as_ref(); - let nft_pid: ProgramId = address.into(); + // get the address and ProgramId of the created collection + let address_nft = state[0].0; + let address_nft_list = address_nft.as_ref(); + let nft_pid: ProgramId = address_nft_list.into(); + // Check collection state let state = get_all_state_nft(&api, &nft_pid) .await .expect("Unexpected invalid state."); + assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); - assert_eq!(state.collection_owner, ALICE.into(), "Wrong Admin"); - println!("NFT Collection STATE: {:?}", state); - - let gas_info = api - .calculate_handle_gas(None, nft_pid, NftAction::Mint.encode(), 0, true) - .await?; - - println!("GAS_INFO MIN LIMIT: {:?}", gas_info.min_limit.clone()); - let (message_id, _) = api - .send_message(nft_pid, NftAction::Mint, gas_info.min_limit * 2, 0) - .await?; - + let percent_to_marketplace = 10_000_000_000_000 * royalty_to_marketplace as u128 / 10_000; + let payment_for_mint = 10_000_000_000_000 + percent_to_marketplace; + let message_id = mint(&api, program_id, address_nft, payment_for_mint) + .await + .expect("Error mint"); assert!(listener.message_processed(message_id).await?.succeed()); - assert!(listener.blocks_running().await?); + // Check marketplace balance + let marketplace_balance_after_mint = api.total_balance(program_id).await?; + assert_eq!( + marketplace_balance_after_mint, + marketplace_balance + percent_to_marketplace, + "Wrong value" + ); + + // Check success of mint let state = get_all_state_nft(&api, &nft_pid) .await .expect("Unexpected invalid state."); @@ -619,104 +292,64 @@ async fn auction_test() -> Result<()> { assert!(!state.tokens.is_empty()); assert_eq!(state.img_links_and_data.len(), 9); - let address_marketplace: ActorId = program_id.into_bytes().into(); - let gas_info = api - .calculate_handle_gas( - None, - nft_pid, - NftAction::Approve { - to: address_marketplace, - token_id: 0, - } - .encode(), - 0, - true, - ) - .await?; - - println!("GAS_INFO MIN LIMIT: {:?}", gas_info.min_limit.clone()); - let (message_id, _) = api - .send_message( - nft_pid, - NftAction::Approve { - to: address_marketplace, - token_id: 0, - }, - gas_info.min_limit * 2, - 0, - ) - .await?; - + // Approve to marketplace + let message_id = approve(&api, program_id, nft_pid) + .await + .expect("Error approve"); assert!(listener.message_processed(message_id).await?.succeed()); - assert!(listener.blocks_running().await?); + // Create auction + let duration_in_block = 12; + let duration_in_secs = 36; // 36 secs let create_auction_payload = NftMarketplaceAction::CreateAuction { collection_address: address_nft, token_id: 0, min_price: 11_000_000_000_000, - duration_ms: 50_000, + duration: duration_in_block, }; let gas_info = api .calculate_handle_gas(None, program_id, create_auction_payload.encode(), 0, true) .await?; - - println!("GAS_INFO MIN LIMIT: {:?}", gas_info.min_limit.clone()); let (message_id, _) = api - .send_message(program_id, create_auction_payload, 30_000_000_000, 0) + .send_message(program_id, create_auction_payload, gas_info.min_limit, 0) .await?; + let start = Instant::now(); assert!(listener.message_processed(message_id).await?.succeed()); - assert!(listener.blocks_running().await?); + // Check state let state = get_marketplace_state(&api, &program_id) .await .expect("Unexpected invalid state."); - println!("\nSTATE: {:?}", state); - - let alice_balance = api.total_balance(api.account_id()).await?; - let amount = alice_balance / 10; - println!("AMOUNT: {:?}", amount.clone()); - api.transfer( - api.get_specific_actor_id(USERS_STR[0]) - .encode() - .as_slice() - .try_into() - .expect("Unexpected invalid `ProgramId`."), - amount, - ) - .await?; - - let client = api - .clone() - .with(USERS_STR[0]) - .expect("Unable to change signer."); + assert!(!state.auctions.is_empty()); + // Add bid + let client = get_new_client(&api, USERS_STR[0]).await; + let mut client_listener = client.subscribe().await?; let add_bid_payload = NftMarketplaceAction::AddBid { collection_address: address_nft, token_id: 0, }; + + let bid = 150_000_000_000_000; + let percent_to_marketplace = bid * royalty_to_marketplace as u128 / 10_000; + let gas_info = api - .calculate_handle_gas(None, program_id, add_bid_payload.encode(), 0, true) + .calculate_handle_gas(None, program_id, add_bid_payload.encode(), bid, true) .await?; - - println!("GAS_INFO MIN LIMIT: {:?}", gas_info.min_limit.clone()); let (message_id, _) = client - .send_message( - program_id, - add_bid_payload, - gas_info.min_limit * 2, - 150_000_000_000_000, - ) + .send_message(program_id, add_bid_payload, gas_info.min_limit, bid) .await?; - let state = get_marketplace_state(&api, &program_id) - .await - .expect("Unexpected invalid state."); - println!("\nSTATE: {:?}", state); - // assert!(client_listener.message_processed(message_id).await?.succeed()); - // assert!(client_listener.blocks_running().await?); + assert!(client_listener + .message_processed(message_id) + .await? + .succeed()); + + let duration = start.elapsed().as_secs(); + std::thread::sleep(std::time::Duration::from_secs(duration_in_secs - duration)); - std::thread::sleep(std::time::Duration::from_secs(10)); + // Check success of close auction let state = get_all_state_nft(&api, &nft_pid) .await .expect("Unexpected invalid state."); @@ -725,6 +358,14 @@ async fn auction_test() -> Result<()> { assert_eq!(token.0, 0); assert_eq!(token.1.owner, api.get_specific_actor_id(USERS_STR[0])); + // Check marketplace balance + let marketplace_balance_after_auction = api.total_balance(program_id).await?; + assert_eq!( + marketplace_balance_after_auction, + marketplace_balance_after_mint + percent_to_marketplace, + "Wrong value" + ); + Ok(()) } @@ -734,152 +375,62 @@ async fn offer_test() -> Result<()> { let api = GearApi::dev_from_path("target/tmp/gear").await?; let mut listener = api.subscribe().await?; // Subscribing for events. - - // Checking that blocks still running. assert!(listener.blocks_running().await?); - let init_marketplace = NftMarketplaceInit { - gas_for_creation: 200_000_000_000, - gas_for_transfer_token: 5_000_000_000, - gas_for_close_auction: 10_000_000_000, - gas_for_delete_collection: 5_000_000_000, - gas_for_get_token_info: 5_000_000_000, - time_between_create_collections: 3_600_000, // 1 hour in milliseconds - minimum_transfer_value: 10_000_000_000_000, - ms_in_block: 3_000, - } - .encode(); - - let path = "target/wasm32-unknown-unknown/debug/nft_marketplace.opt.wasm"; - - let gas_info = api - .calculate_upload_gas( - None, - gclient::code_from_os(path)?, - init_marketplace.clone(), - 0, - true, - ) - .await?; - - let (message_id, program_id, _hash) = api - .upload_program_bytes( - gclient::code_from_os(path)?, - gclient::now_micros().to_le_bytes(), - init_marketplace, - gas_info.min_limit, - 0, - ) - .await?; + let royalty_to_marketplace = 200; + let (message_id, program_id) = init_marketplace(&api) + .await + .expect("Error init marketplace"); assert!(listener.message_processed(message_id).await?.succeed()); - let (nft_code_id, _) = api - .upload_code_by_path("target/wasm32-unknown-unknown/debug/nft.opt.wasm") - .await?; - - let nft_code_id: [u8; 32] = nft_code_id.into(); - - let add_collection_payload = NftMarketplaceAction::AddNewCollection { - code_id: nft_code_id.into(), - meta_link: String::from("My Meta"), - type_name: String::from("Simple NFT"), - type_description: String::from("My Collection"), - }; - - let gas_info = api - .calculate_handle_gas(None, program_id, add_collection_payload.encode(), 0, true) - .await?; - - let (message_id, _) = api - .send_message(program_id, add_collection_payload, gas_info.min_limit, 0) - .await?; - + let message_id = add_new_collection(&api, program_id) + .await + .expect("Error add new collection"); assert!(listener.message_processed(message_id).await?.succeed()); - let img_data = ImageData { - limit_copies: Some(1), - auto_changing_rules: None, - }; - let img_links_and_data: Vec<(String, ImageData)> = (0..10) - .map(|i| (format!("Img-{}", i), img_data.clone())) - .collect(); - - // Successful creation of a new collection - let init_nft_payload = NftInit { - collection_owner: ALICE.into(), - config: Config { - name: "User Collection".to_string(), - description: "User Collection".to_string(), - collection_banner: "Collection banner".to_string(), - collection_logo: "Collection logo".to_string(), - collection_tags: vec!["tag1".to_string()], - additional_links: None, - royalty: 1_000, - user_mint_limit: Some(3), - payment_for_mint: 0, - transferable: Some(0), - sellable: Some(0), - }, - img_links_and_data, - permission_to_mint: None, - } - .encode(); - - let create_collection_payload = NftMarketplaceAction::CreateCollection { - type_name: String::from("Simple NFT"), - payload: init_nft_payload, - }; - let gas_info = api - .calculate_handle_gas( - None, - program_id, - create_collection_payload.encode(), - 0, - true, - ) - .await?; - - println!("GAS_INFO MIN LIMIT: {:?}", gas_info.min_limit.clone()); - let (message_id, _) = api - .send_message(program_id, create_collection_payload, gas_info.min_limit, 0) - .await?; - + let message_id = create_collection(&api, program_id) + .await + .expect("Error create collection"); assert!(listener.message_processed(message_id).await?.succeed()); - assert!(listener.blocks_running().await?); + // Check marketplace balance + let marketplace_balance = api.total_balance(program_id).await?; + assert_eq!(marketplace_balance, 10_000_000_000_000, "Wrong value"); + + // Check that collection created let state = get_all_collection_state(&api, &program_id) .await .expect("Unexpected invalid state."); - assert!(!state.is_empty(), "Collections shouldn't be empty"); - println!("Collections: {:?}", state); - let address_nft = state[0].0; - let address = address_nft.as_ref(); - //let address_nft = hex::decode(address_nft).unwrap(); - //let nft_pid = ProgramId::decode(&mut address_nft).unwrap(); - let nft_pid: ProgramId = address.into(); + // get the address and ProgramId of the created collection + let address_nft = state[0].0; + let address_nft_list = address_nft.as_ref(); + let nft_pid: ProgramId = address_nft_list.into(); + // Check collection state let state = get_all_state_nft(&api, &nft_pid) .await .expect("Unexpected invalid state."); + assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); - assert_eq!(state.collection_owner, ALICE.into(), "Wrong Admin"); - println!("NFT Collection STATE: {:?}", state); - - let gas_info = api - .calculate_handle_gas(None, nft_pid, NftAction::Mint.encode(), 0, true) - .await?; - - println!("GAS_INFO MIN LIMIT: {:?}", gas_info.min_limit.clone()); - let (message_id, _) = api - .send_message(nft_pid, NftAction::Mint, gas_info.min_limit * 2, 0) - .await?; - + let percent_to_marketplace = 10_000_000_000_000 * royalty_to_marketplace as u128 / 10_000; + let payment_for_mint = 10_000_000_000_000 + percent_to_marketplace; + let message_id = mint(&api, program_id, address_nft, payment_for_mint) + .await + .expect("Error mint"); assert!(listener.message_processed(message_id).await?.succeed()); - assert!(listener.blocks_running().await?); + // Check marketplace balance + let marketplace_balance_after_mint = api.total_balance(program_id).await?; + assert_eq!( + marketplace_balance_after_mint, + marketplace_balance + percent_to_marketplace, + "Wrong value" + ); + + // Check success of mint let state = get_all_state_nft(&api, &nft_pid) .await .expect("Unexpected invalid state."); @@ -887,41 +438,32 @@ async fn offer_test() -> Result<()> { assert!(!state.tokens.is_empty()); assert_eq!(state.img_links_and_data.len(), 9); - let alice_balance = api.total_balance(api.account_id()).await?; - let amount = alice_balance / 10; - - api.transfer( - api.get_specific_actor_id(USERS_STR[0]) - .encode() - .as_slice() - .try_into() - .expect("Unexpected invalid `ProgramId`."), - amount, - ) - .await?; - - let client = api - .clone() - .with(USERS_STR[0]) - .expect("Unable to change signer."); - + // Create offer + let client = get_new_client(&api, USERS_STR[0]).await; let mut client_listener = client.subscribe().await?; let create_offer_payload = NftMarketplaceAction::CreateOffer { collection_address: address_nft, token_id: 0, }; + let offer_price = 150_000_000_000_000; + let percent_to_marketplace = offer_price * royalty_to_marketplace as u128 / 10_000; + let gas_info = client - .calculate_handle_gas(None, program_id, create_offer_payload.encode(), 0, true) + .calculate_handle_gas( + None, + program_id, + create_offer_payload.encode(), + offer_price, + true, + ) .await?; - - println!("GAS_INFO MIN LIMIT: {:?}", gas_info.min_limit.clone()); let (message_id, _) = client .send_message( program_id, create_offer_payload, - gas_info.min_limit * 2, - 150_000_000_000_000, + gas_info.min_limit, + offer_price, ) .await?; @@ -929,118 +471,55 @@ async fn offer_test() -> Result<()> { .message_processed(message_id) .await? .succeed()); - assert!(client_listener.blocks_running().await?); + // Check state let state = get_marketplace_state(&api, &program_id) .await .expect("Unexpected invalid state."); - assert!(!state.offers.is_empty()); - println!("\nSTATE: {:?}", state); - - let address_marketplace: ActorId = program_id.into_bytes().into(); - let gas_info = api - .calculate_handle_gas( - None, - nft_pid, - NftAction::Approve { - to: address_marketplace, - token_id: 0, - } - .encode(), - 0, - true, - ) - .await?; - - println!("GAS_INFO MIN LIMIT: {:?}", gas_info.min_limit.clone()); - let (message_id, _) = api - .send_message( - nft_pid, - NftAction::Approve { - to: address_marketplace, - token_id: 0, - }, - gas_info.min_limit * 2, - 0, - ) - .await?; + // Approve to marketplace + let message_id = approve(&api, program_id, nft_pid) + .await + .expect("Error approve"); assert!(listener.message_processed(message_id).await?.succeed()); - assert!(listener.blocks_running().await?); + // Accept offer let offer = Offer { collection_address: address_nft, token_id: 0, creator: api.get_specific_actor_id(USERS_STR[0]), }; - let accept_offer_payload = NftMarketplaceAction::AcceptOffer { offer }; let gas_info = api .calculate_handle_gas(None, program_id, accept_offer_payload.encode(), 0, true) .await?; - - println!("GAS_INFO MIN LIMIT: {:?}", gas_info.min_limit.clone()); let (message_id, _) = api - .send_message(program_id, accept_offer_payload, 15_000_000_000, 0) + .send_message(program_id, accept_offer_payload, gas_info.min_limit, 0) .await?; - assert!(listener.message_processed(message_id).await?.succeed()); - assert!(listener.blocks_running().await?); + // Check state offers let state = get_marketplace_state(&api, &program_id) .await .expect("Unexpected invalid state."); - - println!("\nSTATE: {:?}", state); assert!(state.offers.is_empty()); + // Check success of accept offer let state = get_all_state_nft(&api, &nft_pid) .await .expect("Unexpected invalid state."); - let token = state.tokens.get(0).expect("Can't be None"); assert_eq!(token.0, 0); assert_eq!(token.1.owner, api.get_specific_actor_id(USERS_STR[0])); - Ok(()) -} - -pub async fn get_all_state_nft(api: &GearApi, program_id: &ProgramId) -> Option { - let reply = api - .read_state(*program_id, StateQueryNft::All.encode()) - .await - .expect("Unexpected invalid reply."); - if let StateReplyNft::All(state) = reply { - Some(state) - } else { - None - } -} + // Check balance + let marketplace_balance_after_offer = api.total_balance(program_id).await?; + assert_eq!( + marketplace_balance_after_offer, + marketplace_balance_after_mint + percent_to_marketplace, + "Wrong value" + ); -pub async fn get_all_collection_state( - api: &GearApi, - program_id: &ProgramId, -) -> Option> { - let reply = api - .read_state(*program_id, StateQuery::AllCollections.encode()) - .await - .expect("Unexpected invalid reply."); - if let StateReply::AllCollections(state) = reply { - Some(state) - } else { - None - } -} - -pub async fn get_marketplace_state(api: &GearApi, program_id: &ProgramId) -> Option { - let reply = api - .read_state(*program_id, StateQuery::All.encode()) - .await - .expect("Unexpected invalid reply."); - if let StateReply::All(state) = reply { - Some(state) - } else { - None - } + Ok(()) } diff --git a/tests/test_composable_nft.rs b/tests/test_composable_nft.rs index 68c6e86..34a251d 100644 --- a/tests/test_composable_nft.rs +++ b/tests/test_composable_nft.rs @@ -1,261 +1,261 @@ -use crate::utils::{add_new_collection, buy, create_collection, init_marketplace, sale}; -use utils::prelude::*; -mod utils; -use composable_nft_io::{ - ComposableNftAction, ComposableNftEvent, ComposableNftInit, Config, - StateQuery as StateQueryNft, StateReply as StateReplyNft, -}; -use nft_marketplace_io::*; +// use crate::utils::{add_new_collection, buy, create_collection, init_marketplace, sale}; +// use utils::prelude::*; +// mod utils; +// use composable_nft_io::{ +// ComposableNftAction, ComposableNftEvent, ComposableNftInit, Config, +// StateQuery as StateQueryNft, StateReply as StateReplyNft, +// }; +// use nft_marketplace_io::*; -const USERS: &[u64] = &[5, 6, 7, 8]; +// const USERS: &[u64] = &[5, 6, 7, 8]; -#[test] -fn successful_basics() { - let sys = utils::initialize_system(); - init_marketplace(&sys); - let marketplace = sys.get_program(1); - let nft_collection_code_id = - sys.submit_code("target/wasm32-unknown-unknown/debug/composable_nft.opt.wasm"); +// #[test] +// fn successful_basics() { +// let sys = utils::initialize_system(); +// init_marketplace(&sys); +// let marketplace = sys.get_program(1); +// let nft_collection_code_id = +// sys.submit_code("target/wasm32-unknown-unknown/debug/composable_nft.opt.wasm"); - let state_reply = marketplace - .read_state(StateQuery::CollectionsInfo) - .expect("Unexpected invalid state."); - if let StateReply::CollectionsInfo(state) = state_reply { - assert!(state.is_empty(), "Collection info should be empty"); - println!("Collection info: {:?}", state); - } - // Successful addition of a new collection - let name_composable_nft = "Composable NFT".to_string(); - let res = add_new_collection( - &marketplace, - ADMINS[0], - nft_collection_code_id.into_bytes().into(), - name_composable_nft.clone(), - ); - assert!(!res.main_failed()); - let state_reply = marketplace - .read_state(StateQuery::CollectionsInfo) - .expect("Unexpected invalid state."); - if let StateReply::CollectionsInfo(state) = state_reply { - assert!(!state.is_empty(), "Collection info shouldn't be empty"); - println!("Collection info: {:?}", state); - } +// let state_reply = marketplace +// .read_state(StateQuery::CollectionsInfo) +// .expect("Unexpected invalid state."); +// if let StateReply::CollectionsInfo(state) = state_reply { +// assert!(state.is_empty(), "Collection info should be empty"); +// println!("Collection info: {:?}", state); +// } +// // Successful addition of a new collection +// let name_composable_nft = "Composable NFT".to_string(); +// let res = add_new_collection( +// &marketplace, +// ADMINS[0], +// nft_collection_code_id.into_bytes().into(), +// name_composable_nft.clone(), +// ); +// assert!(!res.main_failed()); +// let state_reply = marketplace +// .read_state(StateQuery::CollectionsInfo) +// .expect("Unexpected invalid state."); +// if let StateReply::CollectionsInfo(state) = state_reply { +// assert!(!state.is_empty(), "Collection info shouldn't be empty"); +// println!("Collection info: {:?}", state); +// } - // Successful creation of a new collection +// // Successful creation of a new collection - let mut img_links: Vec> = Vec::with_capacity(4); +// let mut img_links: Vec> = Vec::with_capacity(4); - for k in 0..3 { - let inner_vec: Vec = (0..10).map(|i| format!("img{}-{}", k, i)).collect(); +// for k in 0..3 { +// let inner_vec: Vec = (0..10).map(|i| format!("img{}-{}", k, i)).collect(); - img_links.push(inner_vec); - } +// img_links.push(inner_vec); +// } - let init_nft_payload = ComposableNftInit { - owner: USERS[0].into(), - config: Config { - name: "User Collection".to_string(), - description: "User Collection".to_string(), - collection_banner: "Collection banner".to_string(), - collection_logo: "Collection logo".to_string(), - collection_tags: vec!["tag1".to_string()], - additional_links: None, - royalty: 0, - payment_for_mint: 0, - user_mint_limit: 3.into(), - tokens_limit: Some(500), - transferable: Some(0), - sellable: Some(0), - }, - img_links, - }; - let res = create_collection( - &marketplace, - USERS[0], - name_composable_nft, - init_nft_payload.encode(), - ); - assert!(!res.main_failed()); - let state_reply = marketplace - .read_state(StateQuery::AllCollections) - .expect("Unexpected invalid state."); - let address_nft = if let StateReply::AllCollections(state) = state_reply { - assert!(!state.is_empty(), "Collections shouldn't be empty"); - println!("Collections: {:?}", state); - state[0].0 - } else { - assert!(false, "Unexpected StateReply variant"); - 0.into() - }; +// let init_nft_payload = ComposableNftInit { +// owner: USERS[0].into(), +// config: Config { +// name: "User Collection".to_string(), +// description: "User Collection".to_string(), +// collection_banner: "Collection banner".to_string(), +// collection_logo: "Collection logo".to_string(), +// collection_tags: vec!["tag1".to_string()], +// additional_links: None, +// royalty: 0, +// payment_for_mint: 0, +// user_mint_limit: 3.into(), +// tokens_limit: Some(500), +// transferable: Some(0), +// sellable: Some(0), +// }, +// img_links, +// }; +// let res = create_collection( +// &marketplace, +// USERS[0], +// name_composable_nft, +// init_nft_payload.encode(), +// ); +// assert!(!res.main_failed()); +// let state_reply = marketplace +// .read_state(StateQuery::AllCollections) +// .expect("Unexpected invalid state."); +// let address_nft = if let StateReply::AllCollections(state) = state_reply { +// assert!(!state.is_empty(), "Collections shouldn't be empty"); +// println!("Collections: {:?}", state); +// state[0].0 +// } else { +// assert!(false, "Unexpected StateReply variant"); +// 0.into() +// }; - let address_nft: [u8; 32] = address_nft.into(); - let nft_collection = sys.get_program(address_nft); - let state_reply = nft_collection - .read_state(StateQueryNft::All) - .expect("Unexpected invalid state."); - if let StateReplyNft::All(state) = state_reply { - assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); - println!("Collection NFT info: {:?}", state); - } +// let address_nft: [u8; 32] = address_nft.into(); +// let nft_collection = sys.get_program(address_nft); +// let state_reply = nft_collection +// .read_state(StateQueryNft::All) +// .expect("Unexpected invalid state."); +// if let StateReplyNft::All(state) = state_reply { +// assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); +// println!("Collection NFT info: {:?}", state); +// } - // Successful mint NFT in the new collection - let res = nft_collection.send( - USERS[1], - ComposableNftAction::Mint { - combination: vec![0, 0, 0], - }, - ); - // let payload = res.log()[0].payload(); - // let expected_payload = ComposableNftEvent::Error("Incorrectly entered combination: wrong combination length".to_owned()).encode(); - // assert_eq!(expected_payload, payload); - assert!(!res.main_failed()); +// // Successful mint NFT in the new collection +// let res = nft_collection.send( +// USERS[1], +// ComposableNftAction::Mint { +// combination: vec![0, 0, 0], +// }, +// ); +// // let payload = res.log()[0].payload(); +// // let expected_payload = ComposableNftEvent::Error("Incorrectly entered combination: wrong combination length".to_owned()).encode(); +// // assert_eq!(expected_payload, payload); +// assert!(!res.main_failed()); - let state_reply = nft_collection - .read_state(StateQueryNft::All) - .expect("Unexpected invalid state."); - if let StateReplyNft::All(state) = state_reply { - assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); - let (owner, token_id) = state.owners.get(0).expect("Can't be None"); - assert_eq!(*owner, USERS[1].into(), "Wrong owner"); - assert_eq!(*token_id, vec![0], "Wrong token id"); - println!("TOKEN INFO: {:?}", state.tokens); - } -} +// let state_reply = nft_collection +// .read_state(StateQueryNft::All) +// .expect("Unexpected invalid state."); +// if let StateReplyNft::All(state) = state_reply { +// assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); +// let (owner, token_id) = state.owners.get(0).expect("Can't be None"); +// assert_eq!(*owner, USERS[1].into(), "Wrong owner"); +// assert_eq!(*token_id, vec![0], "Wrong token id"); +// println!("TOKEN INFO: {:?}", state.tokens); +// } +// } -#[test] -fn composable_sale_success() { - let sys = utils::initialize_system(); - init_marketplace(&sys); - let marketplace = sys.get_program(1); - let nft_collection_code_id = - sys.submit_code("target/wasm32-unknown-unknown/debug/composable_nft.opt.wasm"); +// #[test] +// fn composable_sale_success() { +// let sys = utils::initialize_system(); +// init_marketplace(&sys); +// let marketplace = sys.get_program(1); +// let nft_collection_code_id = +// sys.submit_code("target/wasm32-unknown-unknown/debug/composable_nft.opt.wasm"); - let state_reply = marketplace - .read_state(StateQuery::CollectionsInfo) - .expect("Unexpected invalid state."); - if let StateReply::CollectionsInfo(state) = state_reply { - assert!(state.is_empty(), "Collection info should be empty"); - println!("Collection info: {:?}", state); - } - // Сreating a new type of collection - let name_composable_nft = "Composable NFT".to_string(); +// let state_reply = marketplace +// .read_state(StateQuery::CollectionsInfo) +// .expect("Unexpected invalid state."); +// if let StateReply::CollectionsInfo(state) = state_reply { +// assert!(state.is_empty(), "Collection info should be empty"); +// println!("Collection info: {:?}", state); +// } +// // Сreating a new type of collection +// let name_composable_nft = "Composable NFT".to_string(); - let res = add_new_collection( - &marketplace, - ADMINS[0], - nft_collection_code_id.into_bytes().into(), - name_composable_nft.clone(), - ); - assert!(!res.main_failed()); - let state_reply = marketplace - .read_state(StateQuery::CollectionsInfo) - .expect("Unexpected invalid state."); - if let StateReply::CollectionsInfo(state) = state_reply { - assert!(!state.is_empty(), "Collection info shouldn't be empty"); - // assert_eq!(state[0].1.collection_id, 1, "Collection id should be equal by 0"); - println!("Collection info: {:?}", state); - } +// let res = add_new_collection( +// &marketplace, +// ADMINS[0], +// nft_collection_code_id.into_bytes().into(), +// name_composable_nft.clone(), +// ); +// assert!(!res.main_failed()); +// let state_reply = marketplace +// .read_state(StateQuery::CollectionsInfo) +// .expect("Unexpected invalid state."); +// if let StateReply::CollectionsInfo(state) = state_reply { +// assert!(!state.is_empty(), "Collection info shouldn't be empty"); +// // assert_eq!(state[0].1.collection_id, 1, "Collection id should be equal by 0"); +// println!("Collection info: {:?}", state); +// } - // Create collection +// // Create collection - let mut img_links: Vec> = Vec::with_capacity(4); +// let mut img_links: Vec> = Vec::with_capacity(4); - for k in 0..3 { - let inner_vec: Vec = (0..10).map(|i| format!("img{}-{}", k, i)).collect(); +// for k in 0..3 { +// let inner_vec: Vec = (0..10).map(|i| format!("img{}-{}", k, i)).collect(); - img_links.push(inner_vec); - } +// img_links.push(inner_vec); +// } - let init_nft_payload = ComposableNftInit { - owner: USERS[0].into(), - config: Config { - name: "User Collection".to_string(), - description: "User Collection".to_string(), - collection_banner: "Collection banner".to_string(), - collection_logo: "Collection logo".to_string(), - collection_tags: vec!["tag1".to_string()], - additional_links: None, - royalty: 0, - payment_for_mint: 0, - user_mint_limit: 3.into(), - tokens_limit: Some(500), - transferable: Some(0), - sellable: Some(0), - }, - img_links, - }; - let res = create_collection( - &marketplace, - USERS[0], - name_composable_nft, - init_nft_payload.encode(), - ); - assert!(!res.main_failed()); - let state_reply = marketplace - .read_state(StateQuery::AllCollections) - .expect("Unexpected invalid state."); - let address_nft = if let StateReply::AllCollections(state) = state_reply { - assert!(!state.is_empty(), "Collections shouldn't be empty"); - println!("Collections: {:?}", state); - state[0].0 - } else { - assert!(false, "Unexpected StateReply variant"); - 0.into() - }; +// let init_nft_payload = ComposableNftInit { +// owner: USERS[0].into(), +// config: Config { +// name: "User Collection".to_string(), +// description: "User Collection".to_string(), +// collection_banner: "Collection banner".to_string(), +// collection_logo: "Collection logo".to_string(), +// collection_tags: vec!["tag1".to_string()], +// additional_links: None, +// royalty: 0, +// payment_for_mint: 0, +// user_mint_limit: 3.into(), +// tokens_limit: Some(500), +// transferable: Some(0), +// sellable: Some(0), +// }, +// img_links, +// }; +// let res = create_collection( +// &marketplace, +// USERS[0], +// name_composable_nft, +// init_nft_payload.encode(), +// ); +// assert!(!res.main_failed()); +// let state_reply = marketplace +// .read_state(StateQuery::AllCollections) +// .expect("Unexpected invalid state."); +// let address_nft = if let StateReply::AllCollections(state) = state_reply { +// assert!(!state.is_empty(), "Collections shouldn't be empty"); +// println!("Collections: {:?}", state); +// state[0].0 +// } else { +// assert!(false, "Unexpected StateReply variant"); +// 0.into() +// }; - let address_nft_2: [u8; 32] = address_nft.into(); - let nft_collection = sys.get_program(address_nft_2); +// let address_nft_2: [u8; 32] = address_nft.into(); +// let nft_collection = sys.get_program(address_nft_2); - // Successful mint NFT in the new collection - let res = nft_collection.send( - USERS[1], - ComposableNftAction::Mint { - combination: vec![0, 0, 0], - }, - ); - assert!(!res.main_failed()); +// // Successful mint NFT in the new collection +// let res = nft_collection.send( +// USERS[1], +// ComposableNftAction::Mint { +// combination: vec![0, 0, 0], +// }, +// ); +// assert!(!res.main_failed()); - // Successful approve NFT in the collection - let addres_marketplace: [u8; 32] = marketplace.id().into(); - let res = nft_collection.send( - USERS[1], - ComposableNftAction::Approve { - to: addres_marketplace.into(), - token_id: 0, - }, - ); - assert!(!res.main_failed()); +// // Successful approve NFT in the collection +// let addres_marketplace: [u8; 32] = marketplace.id().into(); +// let res = nft_collection.send( +// USERS[1], +// ComposableNftAction::Approve { +// to: addres_marketplace.into(), +// token_id: 0, +// }, +// ); +// assert!(!res.main_failed()); - let res = sale(&marketplace, USERS[1], address_nft, 0, 11_000_000_000_000); - let result = &res.decoded_log::>()[0]; - println!("RES: {:?}", result); - assert!(!res.main_failed()); +// let res = sale(&marketplace, USERS[1], address_nft, 0, 11_000_000_000_000); +// let result = &res.decoded_log::>()[0]; +// println!("RES: {:?}", result); +// assert!(!res.main_failed()); - let state_reply = marketplace - .read_state(StateQuery::All) - .expect("Unexpected invalid state."); - if let StateReply::All(state) = state_reply { - println!("STATE: {:?}", state); - } +// let state_reply = marketplace +// .read_state(StateQuery::All) +// .expect("Unexpected invalid state."); +// if let StateReply::All(state) = state_reply { +// println!("STATE: {:?}", state); +// } - sys.mint_to(USERS[2], 11_000_000_000_000); - // sys.mint_to(1, 100_000_000_000_000); - let res = buy(&marketplace, USERS[2], address_nft, 0, 11_000_000_000_000); - assert!(!res.main_failed()); +// sys.mint_to(USERS[2], 11_000_000_000_000); +// // sys.mint_to(1, 100_000_000_000_000); +// let res = buy(&marketplace, USERS[2], address_nft, 0, 11_000_000_000_000); +// assert!(!res.main_failed()); - sys.claim_value_from_mailbox(USERS[1]); +// sys.claim_value_from_mailbox(USERS[1]); - let balance = sys.balance_of(USERS[1]); - assert_eq!(balance, 11_000_000_000_000, "Wrong balance"); +// let balance = sys.balance_of(USERS[1]); +// assert_eq!(balance, 11_000_000_000_000, "Wrong balance"); - let state_reply = nft_collection - .read_state(StateQueryNft::All) - .expect("Unexpected invalid state."); +// let state_reply = nft_collection +// .read_state(StateQueryNft::All) +// .expect("Unexpected invalid state."); - if let StateReplyNft::All(state) = state_reply { - println!("STATE: {:?}", state); - let token = state.tokens.get(0).expect("Can't be None"); - assert_eq!(token.0, 0); - assert_eq!(token.1.owner, USERS[2].into()) - } -} +// if let StateReplyNft::All(state) = state_reply { +// println!("STATE: {:?}", state); +// let token = state.tokens.get(0).expect("Can't be None"); +// assert_eq!(token.0, 0); +// assert_eq!(token.1.owner, USERS[2].into()) +// } +// } diff --git a/tests/test_marketplace.rs b/tests/test_marketplace.rs index a47df3a..0bfe249 100644 --- a/tests/test_marketplace.rs +++ b/tests/test_marketplace.rs @@ -1,6 +1,7 @@ use crate::utils::*; use utils::prelude::*; mod utils; +use gtest::Program; use nft_io::{StateQuery as StateQueryNft, StateReply as StateReplyNft}; use nft_marketplace_io::*; @@ -9,8 +10,8 @@ const USERS: &[u64] = &[5, 6, 7, 8]; #[test] fn create_success() { let sys = utils::initialize_system(); - init_marketplace(&sys); - let marketplace = sys.get_program(1); + let marketplace = Program::init_marketplace(&sys); + let nft_collection_code_id = sys.submit_code("target/wasm32-unknown-unknown/debug/nft.opt.wasm"); @@ -23,13 +24,13 @@ fn create_success() { } // Сreating a new type of collection let name_simple_nft = "Simple NFT".to_string(); - let res = add_new_collection( - &marketplace, + marketplace.add_new_collection( ADMINS[0], nft_collection_code_id.into_bytes().into(), name_simple_nft.clone(), + None, ); - assert!(!res.main_failed()); + let state_reply = marketplace .read_state(StateQuery::CollectionsInfo) .expect("Unexpected invalid state."); @@ -41,8 +42,8 @@ fn create_success() { // Add admin let users: Vec = vec![100.into(), 101.into()]; - let res = add_admin(&marketplace, ADMINS[0], users); - assert!(!res.main_failed()); + marketplace.add_admin(ADMINS[0], users, None); + let state_reply = marketplace .read_state(StateQuery::Admins) .expect("Unexpected invalid state."); @@ -55,8 +56,8 @@ fn create_success() { } // Delete admin - let res = delete_admin(&marketplace, ADMINS[0], 100.into()); - assert!(!res.main_failed()); + marketplace.delete_admin(ADMINS[0], 100.into(), None); + let state_reply = marketplace .read_state(StateQuery::Admins) .expect("Unexpected invalid state."); @@ -65,19 +66,22 @@ fn create_success() { } // Update config - let res = update_config( - &marketplace, + marketplace.update_config( ADMINS[0], Some(200_000_000_000), None, None, None, None, + None, Some(7_200_000), + None, + None, + None, Some(11_000_000_000_000), None, + None, ); - assert!(!res.main_failed()); let state_reply = marketplace .read_state(StateQuery::Config) .expect("Unexpected invalid state."); @@ -92,14 +96,19 @@ fn create_success() { let init_nft_payload = get_init_nft_payload(USERS[0].into(), 0, Some(3), 0, None); + sys.mint_to(USERS[0], 100_000_000_000_000); // Create collection - let res = create_collection( - &marketplace, + marketplace.create_collection( USERS[0], name_simple_nft.clone(), init_nft_payload.encode(), + 10_000_000_000_000, + None, ); - assert!(!res.main_failed()); + // Check marketplace balance + let balance = sys.balance_of(1); + assert_eq!(balance, 10_000_000_000_000, "Wrong balance"); + let state_reply = marketplace .read_state(StateQuery::AllCollections) .expect("Unexpected invalid state."); @@ -108,13 +117,13 @@ fn create_success() { assert_eq!(state[0].1 .1, USERS[0].into(), "Wrong owner of collection"); } sys.spend_blocks(7200); - let res = create_collection( - &marketplace, + marketplace.create_collection( USERS[0], name_simple_nft.clone(), init_nft_payload.encode(), + 10_000_000_000_000, + None, ); - assert!(!res.main_failed()); let state_reply = marketplace .read_state(StateQuery::AllCollections) .expect("Unexpected invalid state."); @@ -133,8 +142,7 @@ fn create_success() { assert!(false, "Unexpected StateReply variant"); 0.into() }; - let res = delete_collection(&marketplace, ADMINS[0], address_nft); - assert!(!res.main_failed()); + marketplace.delete_collection(ADMINS[0], address_nft, None); let state_reply = marketplace .read_state(StateQuery::AllCollections) .expect("Unexpected invalid state."); @@ -146,8 +154,7 @@ fn create_success() { #[test] fn create_failures() { let sys = utils::initialize_system(); - init_marketplace(&sys); - let marketplace = sys.get_program(1); + let marketplace = Program::init_marketplace(&sys); let nft_collection_code_id = sys.submit_code("target/wasm32-unknown-unknown/debug/nft.opt.wasm"); @@ -160,59 +167,63 @@ fn create_failures() { } // not admin let name_simple_nft = "Simple NFT".to_string(); - let res = add_new_collection( - &marketplace, + marketplace.add_new_collection( USERS[0], nft_collection_code_id.into_bytes().into(), name_simple_nft.clone(), + Some(NftMarketplaceError::AccessDenied), ); - check_marketplace_error(USERS[0], &res, NftMarketplaceError::AccessDenied); - - let res = add_admin(&marketplace, USERS[0], vec![100.into()]); - check_marketplace_error(USERS[0], &res, NftMarketplaceError::AccessDenied); + marketplace.add_admin( + USERS[0], + vec![100.into()], + Some(NftMarketplaceError::AccessDenied), + ); - let res = update_config( - &marketplace, + marketplace.update_config( USERS[0], Some(200_000_000_000), None, None, None, None, + None, Some(7_200_000), + None, + None, + None, Some(11_000_000_000_000), None, + Some(NftMarketplaceError::AccessDenied), ); - check_marketplace_error(USERS[0], &res, NftMarketplaceError::AccessDenied); // Add type of collection - let res = add_new_collection( - &marketplace, + marketplace.add_new_collection( ADMINS[0], nft_collection_code_id.into_bytes().into(), name_simple_nft.clone(), + None, ); - assert!(!res.main_failed()); let init_nft_payload = get_init_nft_payload(USERS[0].into(), 0, Some(3), 0, None); // Сan only create one collection per hour - let res = create_collection( - &marketplace, + sys.mint_to(USERS[0], 100_000_000_000_000); + marketplace.create_collection( USERS[0], name_simple_nft.clone(), init_nft_payload.encode(), + 10_000_000_000_000, + None, ); - assert!(!res.main_failed()); sys.spend_blocks(1100); // less than 3,600 - let res = create_collection( - &marketplace, + marketplace.create_collection( USERS[0], name_simple_nft.clone(), init_nft_payload.encode(), + 10_000_000_000_000, + Some(NftMarketplaceError::DeadlineError), ); - check_marketplace_error(USERS[0], &res, NftMarketplaceError::DeadlineError); // Delete collection let state_reply = marketplace @@ -224,31 +235,31 @@ fn create_failures() { assert!(false, "Unexpected StateReply variant"); 0.into() }; - let res = delete_collection(&marketplace, USERS[0], 1.into()); - assert!(!res.main_failed()); - check_marketplace_error(USERS[0], &res, NftMarketplaceError::WrongCollectionAddress); - let res = delete_collection(&marketplace, USERS[1], address_nft); - assert!(!res.main_failed()); - check_marketplace_error(USERS[1], &res, NftMarketplaceError::AccessDenied); - - // Mint token - let address_nft_array: [u8; 32] = address_nft.into(); - let nft_collection = sys.get_program(address_nft_array); + marketplace.delete_collection( + USERS[0], + 1.into(), + Some(NftMarketplaceError::WrongCollectionAddress), + ); + marketplace.delete_collection( + USERS[1], + address_nft, + Some(NftMarketplaceError::AccessDenied), + ); // Successful mint NFT in the new collection - let res = nft_collection.send(USERS[1], nft_io::NftAction::Mint); - assert!(!res.main_failed()); + marketplace.mint(USERS[1], address_nft, None); - let res = delete_collection(&marketplace, USERS[0], address_nft); - assert!(!res.main_failed()); - check_marketplace_error(USERS[0], &res, NftMarketplaceError::AccessDenied); + marketplace.delete_collection( + USERS[0], + address_nft, + Some(NftMarketplaceError::AccessDenied), + ); } #[test] fn sale_success() { let sys = utils::initialize_system(); - init_marketplace(&sys); - let marketplace = sys.get_program(1); + let marketplace = Program::init_marketplace(&sys); let nft_collection_code_id = sys.submit_code("target/wasm32-unknown-unknown/debug/nft.opt.wasm"); @@ -261,13 +272,13 @@ fn sale_success() { } // Сreating a new type of collection let name_simple_nft = "Simple NFT".to_string(); - let res = add_new_collection( - &marketplace, + marketplace.add_new_collection( ADMINS[0], nft_collection_code_id.into_bytes().into(), name_simple_nft.clone(), + None, ); - assert!(!res.main_failed()); + let state_reply = marketplace .read_state(StateQuery::CollectionsInfo) .expect("Unexpected invalid state."); @@ -280,14 +291,14 @@ fn sale_success() { // Create collection let royalty = 1_000; let init_nft_payload = get_init_nft_payload(USERS[0].into(), royalty, Some(3), 0, None); - - let res = create_collection( - &marketplace, + sys.mint_to(USERS[0], 100_000_000_000_000); + marketplace.create_collection( USERS[0], name_simple_nft.clone(), init_nft_payload.encode(), + 10_000_000_000_000, + None, ); - assert!(!res.main_failed()); let state_reply = marketplace .read_state(StateQuery::AllCollections) .expect("Unexpected invalid state."); @@ -304,8 +315,7 @@ fn sale_success() { let nft_collection = sys.get_program(address_nft_2); // Successful mint NFT in the new collection - let res = nft_collection.send(USERS[1], nft_io::NftAction::Mint); - assert!(!res.main_failed()); + marketplace.mint(USERS[1], address_nft, None); // Successful approve NFT in the collection let addres_marketplace: [u8; 32] = marketplace.id().into(); @@ -320,8 +330,7 @@ fn sale_success() { let price = 150_000_000_000_000; - let res = sale(&marketplace, USERS[1], address_nft, 0, price); - assert!(!res.main_failed()); + marketplace.sale(USERS[1], address_nft, 0, price, None); let state_reply = marketplace .read_state(StateQuery::All) @@ -333,21 +342,34 @@ fn sale_success() { sys.mint_to(USERS[2], price); // sys.mint_to(1, 100_000_000_000_000); - let res = buy(&marketplace, USERS[2], address_nft, 0, price); - assert!(!res.main_failed()); + marketplace.buy(USERS[2], address_nft, 0, price, None); let percent_to_collection_owner = price * royalty as u128 / 10_000; + let percent_to_marketplace = price * 200 as u128 / 10_000; sys.claim_value_from_mailbox(USERS[1]); let balance = sys.balance_of(USERS[1]); assert_eq!( balance, - price - percent_to_collection_owner, + price - percent_to_collection_owner - percent_to_marketplace, "Wrong balance" ); + let old_balance = sys.balance_of(USERS[0]); sys.claim_value_from_mailbox(USERS[0]); let balance = sys.balance_of(USERS[0]); - assert_eq!(balance, percent_to_collection_owner, "Wrong balance"); + assert_eq!( + balance - old_balance, + percent_to_collection_owner, + "Wrong balance" + ); + + let balance = sys.balance_of(1); + + assert_eq!( + balance, + percent_to_marketplace + 10_000_000_000_000, + "Wrong balance" + ); let state_reply = nft_collection .read_state(StateQueryNft::All) @@ -364,8 +386,7 @@ fn sale_success() { #[test] fn sale_failures() { let sys = utils::initialize_system(); - init_marketplace(&sys); - let marketplace = sys.get_program(1); + let marketplace = Program::init_marketplace(&sys); let nft_collection_code_id = sys.submit_code("target/wasm32-unknown-unknown/debug/nft.opt.wasm"); @@ -378,25 +399,26 @@ fn sale_failures() { } // Сreating a new type of collection let name_simple_nft = "Simple NFT".to_string(); - let res = add_new_collection( - &marketplace, + marketplace.add_new_collection( ADMINS[0], nft_collection_code_id.into_bytes().into(), name_simple_nft.clone(), + None, ); - assert!(!res.main_failed()); // Create collection let royalty = 1_000; let init_nft_payload = get_init_nft_payload(USERS[0].into(), royalty, Some(3), 0, None); - let res = create_collection( - &marketplace, + sys.mint_to(USERS[0], 100_000_000_000_000); + marketplace.create_collection( USERS[0], name_simple_nft.clone(), init_nft_payload.encode(), + 10_000_000_000_000, + None, ); - assert!(!res.main_failed()); + let state_reply = marketplace .read_state(StateQuery::AllCollections) .expect("Unexpected invalid state."); @@ -413,33 +435,44 @@ fn sale_failures() { let nft_collection = sys.get_program(address_nft_2); // Successful mint NFT in the new collection - let res = nft_collection.send(USERS[1], nft_io::NftAction::Mint); - assert!(!res.main_failed()); + marketplace.mint(USERS[1], address_nft, None); // low price - let res = sale(&marketplace, USERS[1], address_nft, 0, 9_000_000_000_000); - assert!(!res.main_failed()); - check_marketplace_error( + marketplace.sale( USERS[1], - &res, - NftMarketplaceError::LessThanExistentialDeposit, + address_nft, + 0, + 9_000_000_000_000, + Some(NftMarketplaceError::LessThanExistentialDeposit), ); // Only owner can send this action let price = 150_000_000_000_000; - let res = sale(&marketplace, USERS[2], address_nft, 0, price); - assert!(!res.main_failed()); - check_marketplace_error(USERS[2], &res, NftMarketplaceError::AccessDenied); + marketplace.sale( + USERS[2], + address_nft, + 0, + price, + Some(NftMarketplaceError::AccessDenied), + ); // No approve to the marketplace - let res = sale(&marketplace, USERS[1], address_nft, 0, price); - assert!(!res.main_failed()); - check_marketplace_error(USERS[1], &res, NftMarketplaceError::NoApproveToMarketplace); + marketplace.sale( + USERS[1], + address_nft, + 0, + price, + Some(NftMarketplaceError::NoApproveToMarketplace), + ); // wrong collection address - let res = sale(&marketplace, USERS[1], 1.into(), 0, price); - assert!(!res.main_failed()); - check_marketplace_error(USERS[1], &res, NftMarketplaceError::WrongCollectionAddress); + marketplace.sale( + USERS[1], + 1.into(), + 0, + price, + Some(NftMarketplaceError::WrongCollectionAddress), + ); // Successful approve NFT in the collection let addres_marketplace: [u8; 32] = marketplace.id().into(); @@ -453,8 +486,7 @@ fn sale_failures() { assert!(!res.main_failed()); // Success - let res = sale(&marketplace, USERS[1], address_nft, 0, price); - assert!(!res.main_failed()); + marketplace.sale(USERS[1], address_nft, 0, price, None); let state_reply = marketplace .read_state(StateQuery::All) @@ -464,47 +496,59 @@ fn sale_failures() { } // is already on sale - let res = sale(&marketplace, USERS[1], address_nft, 0, price); - assert!(!res.main_failed()); - check_marketplace_error(USERS[1], &res, NftMarketplaceError::AlreadyOnSale); + marketplace.sale( + USERS[1], + address_nft, + 0, + price, + Some(NftMarketplaceError::AlreadyOnSale), + ); // wrong owner - let res = cancel_sale(&marketplace, USERS[2], address_nft, 0); - assert!(!res.main_failed()); - check_marketplace_error(USERS[2], &res, NftMarketplaceError::AccessDenied); + marketplace.cancel_sale( + USERS[2], + address_nft, + 0, + Some(NftMarketplaceError::AccessDenied), + ); // Wrong token_id - let res = cancel_sale(&marketplace, USERS[1], address_nft, 1); - assert!(!res.main_failed()); - check_marketplace_error(USERS[1], &res, NftMarketplaceError::SaleDoesNotExist); + marketplace.cancel_sale( + USERS[1], + address_nft, + 1, + Some(NftMarketplaceError::SaleDoesNotExist), + ); // value is less than the price sys.mint_to(USERS[2], price); - let balance = sys.balance_of(1); - println!("BALANCE {:?}", balance); - let res = buy(&marketplace, USERS[2], address_nft, 0, price - 1); - assert!(!res.main_failed()); - check_marketplace_error(USERS[2], &res, NftMarketplaceError::ValueIsLessThanPrice); - - let balance = sys.balance_of(1); - println!("BALANCE {:?}", balance); + marketplace.buy( + USERS[2], + address_nft, + 0, + price - 1, + Some(NftMarketplaceError::ValueIsLessThanPrice), + ); sys.claim_value_from_mailbox(USERS[2]); let balance = sys.balance_of(USERS[2]); assert_eq!(balance, price - 1, "Wrong balance"); // sale does not exist - let res = buy(&marketplace, USERS[2], address_nft, 1, price - 1); - assert!(!res.main_failed()); - check_marketplace_error(USERS[2], &res, NftMarketplaceError::SaleDoesNotExist); + marketplace.buy( + USERS[2], + address_nft, + 1, + price - 1, + Some(NftMarketplaceError::SaleDoesNotExist), + ); } #[test] fn auction_success() { let sys = utils::initialize_system(); - init_marketplace(&sys); - let marketplace = sys.get_program(1); + let marketplace = Program::init_marketplace(&sys); let nft_collection_code_id = sys.submit_code("target/wasm32-unknown-unknown/debug/nft.opt.wasm"); @@ -517,13 +561,13 @@ fn auction_success() { } // Сreating a new type of collection let name_simple_nft = "Simple NFT".to_string(); - let res = add_new_collection( - &marketplace, + marketplace.add_new_collection( ADMINS[0], nft_collection_code_id.into_bytes().into(), name_simple_nft.clone(), + None, ); - assert!(!res.main_failed()); + let state_reply = marketplace .read_state(StateQuery::CollectionsInfo) .expect("Unexpected invalid state."); @@ -537,14 +581,14 @@ fn auction_success() { let royalty = 1_000; let init_nft_payload = get_init_nft_payload(USERS[0].into(), royalty, Some(3), 0, None); - - let res = create_collection( - &marketplace, + sys.mint_to(USERS[0], 100_000_000_000_000); + marketplace.create_collection( USERS[0], name_simple_nft.clone(), init_nft_payload.encode(), + 10_000_000_000_000, + None, ); - assert!(!res.main_failed()); let state_reply = marketplace .read_state(StateQuery::AllCollections) .expect("Unexpected invalid state."); @@ -561,8 +605,7 @@ fn auction_success() { let nft_collection = sys.get_program(address_nft_2); // Successful mint NFT in the new collection - let res = nft_collection.send(USERS[1], nft_io::NftAction::Mint); - assert!(!res.main_failed()); + marketplace.mint(USERS[1], address_nft, None); // Successful approve NFT in the collection let addres_marketplace: [u8; 32] = marketplace.id().into(); @@ -575,19 +618,8 @@ fn auction_success() { ); assert!(!res.main_failed()); - let duration_ms = 10_000; - let duration_blocks = duration_ms / 3000 + 1; - let res = create_auction( - &marketplace, - USERS[1], - address_nft, - 0, - 10_000_000_000_000, - duration_ms, - ); - let result = &res.decoded_log::>()[0]; - println!("RES: {:?}", result); - assert!(!res.main_failed()); + let duration = 10; + marketplace.create_auction(USERS[1], address_nft, 0, 10_300_000_000_000, duration, None); let state_reply = marketplace .read_state(StateQuery::All) @@ -600,8 +632,8 @@ fn auction_success() { sys.mint_to(USERS[2], current_balance); sys.mint_to(USERS[3], current_balance); - let res = add_bid(&marketplace, USERS[2], address_nft, 0, 100_000_000_000_000); - assert!(!res.main_failed()); + marketplace.add_bid(USERS[2], address_nft, 0, 100_000_000_000_000, None); + let balance = sys.balance_of(USERS[2]); assert_eq!(balance, 100_000_000_000_000, "Wrong balance"); @@ -614,8 +646,8 @@ fn auction_success() { } let final_bid = 150_000_000_000_000; - let res = add_bid(&marketplace, USERS[3], address_nft, 0, final_bid); - assert!(!res.main_failed()); + marketplace.add_bid(USERS[3], address_nft, 0, final_bid, None); + let balance = sys.balance_of(USERS[3]); assert_eq!(balance, current_balance - final_bid, "Wrong balance"); sys.claim_value_from_mailbox(USERS[2]); @@ -630,20 +662,33 @@ fn auction_success() { println!("STATE: {:?}", state); } - sys.spend_blocks(duration_blocks); + sys.spend_blocks(duration); let percent_to_collection_owner = final_bid * royalty as u128 / 10_000; + let percent_to_marketplace = final_bid * 200 as u128 / 10_000; sys.claim_value_from_mailbox(USERS[1]); let balance = sys.balance_of(USERS[1]); assert_eq!( balance, - final_bid - percent_to_collection_owner, + final_bid - percent_to_collection_owner - percent_to_marketplace, "Wrong balance" ); + let old_balance = sys.balance_of(USERS[0]); sys.claim_value_from_mailbox(USERS[0]); let balance = sys.balance_of(USERS[0]); - assert_eq!(balance, percent_to_collection_owner, "Wrong balance"); + assert_eq!( + balance - old_balance, + percent_to_collection_owner, + "Wrong balance" + ); + + let balance = sys.balance_of(1); + assert_eq!( + balance, + percent_to_marketplace + 10_000_000_000_000, + "Wrong balance" + ); let state_reply = nft_collection .read_state(StateQueryNft::All) @@ -660,8 +705,7 @@ fn auction_success() { #[test] fn auction_cancel() { let sys = utils::initialize_system(); - init_marketplace(&sys); - let marketplace = sys.get_program(1); + let marketplace = Program::init_marketplace(&sys); let nft_collection_code_id = sys.submit_code("target/wasm32-unknown-unknown/debug/nft.opt.wasm"); @@ -674,13 +718,12 @@ fn auction_cancel() { } // Сreating a new type of collection let name_simple_nft = "Simple NFT".to_string(); - let res = add_new_collection( - &marketplace, + marketplace.add_new_collection( ADMINS[0], nft_collection_code_id.into_bytes().into(), name_simple_nft.clone(), + None, ); - assert!(!res.main_failed()); let state_reply = marketplace .read_state(StateQuery::CollectionsInfo) .expect("Unexpected invalid state."); @@ -693,14 +736,15 @@ fn auction_cancel() { // Create collection let royalty = 1_000; let init_nft_payload = get_init_nft_payload(USERS[0].into(), royalty, Some(3), 0, None); - - let res = create_collection( - &marketplace, + sys.mint_to(USERS[0], 100_000_000_000_000); + marketplace.create_collection( USERS[0], name_simple_nft.clone(), init_nft_payload.encode(), + 10_000_000_000_000, + None, ); - assert!(!res.main_failed()); + let state_reply = marketplace .read_state(StateQuery::AllCollections) .expect("Unexpected invalid state."); @@ -717,8 +761,7 @@ fn auction_cancel() { let nft_collection = sys.get_program(address_nft_2); // Successful mint NFT in the new collection - let res = nft_collection.send(USERS[1], nft_io::NftAction::Mint); - assert!(!res.main_failed()); + marketplace.mint(USERS[1], address_nft, None); // Successful approve NFT in the collection let addres_marketplace: [u8; 32] = marketplace.id().into(); @@ -732,24 +775,21 @@ fn auction_cancel() { assert!(!res.main_failed()); // Create auction - let duration_ms_1 = 10_000; - let duration_blocks_1 = duration_ms_1 / 3000 + 1; - let res = create_auction( - &marketplace, + let duration_1 = 5; + marketplace.create_auction( USERS[1], address_nft, 0, - 10_000_000_000_000, - duration_ms_1, + 10_300_000_000_000, + duration_1, + None, ); - assert!(!res.main_failed()); // Add bid let current_balance = 200_000_000_000_000; sys.mint_to(USERS[2], current_balance); - let res = add_bid(&marketplace, USERS[2], address_nft, 0, 100_000_000_000_000); - assert!(!res.main_failed()); + marketplace.add_bid(USERS[2], address_nft, 0, 100_000_000_000_000, None); let balance = sys.balance_of(USERS[2]); assert_eq!(balance, 100_000_000_000_000, "Wrong balance"); @@ -761,8 +801,7 @@ fn auction_cancel() { } // Cancel auction - let res = cancel_auction(&marketplace, USERS[1], address_nft, 0); - assert!(!res.main_failed()); + marketplace.cancel_auction(USERS[1], address_nft, 0, None); let state_reply = marketplace .read_state(StateQuery::All) @@ -788,17 +827,15 @@ fn auction_cancel() { assert!(!res.main_failed()); // Create the same auction - let duration_ms_2 = 30_000; - let duration_blocks_2 = duration_ms_2 / 3000 + 1; - let res = create_auction( - &marketplace, + let duration_2 = 10; + marketplace.create_auction( USERS[1], address_nft, 0, - 10_000_000_000_000, - duration_ms_2, + 10_300_000_000_000, + duration_2, + None, ); - assert!(!res.main_failed()); let state_reply = marketplace .read_state(StateQuery::All) @@ -807,7 +844,7 @@ fn auction_cancel() { assert!(!state.auctions.is_empty()); } - sys.spend_blocks(duration_blocks_1); + sys.spend_blocks(duration_1); // the delayed message from the first version of the auction will come, // but it should end with an error, because the auction was canceled and a new one was created. @@ -818,7 +855,7 @@ fn auction_cancel() { assert!(!state.auctions.is_empty()); } - sys.spend_blocks(duration_blocks_2 - duration_blocks_1); + sys.spend_blocks(duration_2 - duration_1); let state_reply = marketplace .read_state(StateQuery::All) @@ -842,8 +879,7 @@ fn auction_cancel() { #[test] fn auction_failures() { let sys = utils::initialize_system(); - init_marketplace(&sys); - let marketplace = sys.get_program(1); + let marketplace = Program::init_marketplace(&sys); let nft_collection_code_id = sys.submit_code("target/wasm32-unknown-unknown/debug/nft.opt.wasm"); @@ -856,13 +892,12 @@ fn auction_failures() { } // Сreating a new type of collection let name_simple_nft = "Simple NFT".to_string(); - let res = add_new_collection( - &marketplace, + marketplace.add_new_collection( ADMINS[0], nft_collection_code_id.into_bytes().into(), name_simple_nft.clone(), + None, ); - assert!(!res.main_failed()); let state_reply = marketplace .read_state(StateQuery::CollectionsInfo) .expect("Unexpected invalid state."); @@ -875,14 +910,15 @@ fn auction_failures() { // Create collection let royalty = 1_000; let init_nft_payload = get_init_nft_payload(USERS[0].into(), royalty, Some(3), 0, None); - - let res = create_collection( - &marketplace, + sys.mint_to(USERS[0], 100_000_000_000_000); + marketplace.create_collection( USERS[0], name_simple_nft.clone(), init_nft_payload.encode(), + 10_000_000_000_000, + None, ); - assert!(!res.main_failed()); + let state_reply = marketplace .read_state(StateQuery::AllCollections) .expect("Unexpected invalid state."); @@ -899,51 +935,39 @@ fn auction_failures() { let nft_collection = sys.get_program(address_nft_2); // Successful mint NFT in the new collection - let res = nft_collection.send(USERS[1], nft_io::NftAction::Mint); - assert!(!res.main_failed()); + marketplace.mint(USERS[1], address_nft, None); // low price - let duration_ms = 10_000; - let duration_blocks = duration_ms / 3000 + 1; + let duration = 10; - let res = create_auction( - &marketplace, + marketplace.create_auction( USERS[1], address_nft, 0, 9_000_000_000_000, - duration_ms, - ); - assert!(!res.main_failed()); - check_marketplace_error( - USERS[1], - &res, - NftMarketplaceError::LessThanExistentialDeposit, + duration, + Some(NftMarketplaceError::LessThanExistentialDeposit), ); // Only token owner can send - let res = create_auction( - &marketplace, + marketplace.create_auction( USERS[2], address_nft, 0, - 10_000_000_000_000, - duration_ms, + 10_300_000_000_000, + duration, + Some(NftMarketplaceError::AccessDenied), ); - assert!(!res.main_failed()); - check_marketplace_error(USERS[2], &res, NftMarketplaceError::AccessDenied); // No approve - let res = create_auction( - &marketplace, + marketplace.create_auction( USERS[1], address_nft, 0, - 10_000_000_000_000, - duration_ms, + 10_300_000_000_000, + duration, + Some(NftMarketplaceError::NoApproveToMarketplace), ); - assert!(!res.main_failed()); - check_marketplace_error(USERS[1], &res, NftMarketplaceError::NoApproveToMarketplace); // Successful approve NFT in the collection let addres_marketplace: [u8; 32] = marketplace.id().into(); @@ -957,42 +981,48 @@ fn auction_failures() { assert!(!res.main_failed()); // Successful create auction - let res = create_auction( - &marketplace, + marketplace.create_auction(USERS[1], address_nft, 0, 11_000_000_000_000, duration, None); + + // No auction with this collection address and token id + marketplace.cancel_auction( USERS[1], address_nft, - 0, - 11_000_000_000_000, - duration_ms, + 1, + Some(NftMarketplaceError::ThereIsNoSuchAuction), ); - assert!(!res.main_failed()); - - // No auction with this collection address and token id - let res = cancel_auction(&marketplace, USERS[1], address_nft, 1); - assert!(!res.main_failed()); - check_marketplace_error(USERS[1], &res, NftMarketplaceError::ThereIsNoSuchAuction); // Only the creator of the auction can send cancel_auction - let res = cancel_auction(&marketplace, USERS[2], address_nft, 0); - assert!(!res.main_failed()); - check_marketplace_error(USERS[2], &res, NftMarketplaceError::AccessDenied); + marketplace.cancel_auction( + USERS[2], + address_nft, + 0, + Some(NftMarketplaceError::AccessDenied), + ); let current_balance = 20_000_000_000_000; sys.mint_to(USERS[2], current_balance); - let res = add_bid(&marketplace, USERS[2], address_nft, 0, 10_000_000_000_000); - assert!(!res.main_failed()); - check_marketplace_error(USERS[2], &res, NftMarketplaceError::LessOrEqualThanBid); + marketplace.add_bid( + USERS[2], + address_nft, + 0, + 10_000_000_000_000, + Some(NftMarketplaceError::LessOrEqualThanBid), + ); sys.claim_value_from_mailbox(USERS[2]); let balance = sys.balance_of(USERS[2]); assert_eq!(balance, 20_000_000_000_000, "Wrong balance"); - sys.spend_blocks(duration_blocks); + sys.spend_blocks(duration); sys.mint_to(USERS[3], 15_000_000_000_000); - let res = add_bid(&marketplace, USERS[3], address_nft, 0, 15_000_000_000_000); - assert!(!res.main_failed()); - check_marketplace_error(USERS[3], &res, NftMarketplaceError::ThereIsNoSuchAuction); + marketplace.add_bid( + USERS[3], + address_nft, + 0, + 15_000_000_000_000, + Some(NftMarketplaceError::ThereIsNoSuchAuction), + ); sys.claim_value_from_mailbox(USERS[3]); let balance = sys.balance_of(USERS[3]); @@ -1002,8 +1032,7 @@ fn auction_failures() { #[test] fn offer_success() { let sys = utils::initialize_system(); - init_marketplace(&sys); - let marketplace = sys.get_program(1); + let marketplace = Program::init_marketplace(&sys); let nft_collection_code_id = sys.submit_code("target/wasm32-unknown-unknown/debug/nft.opt.wasm"); @@ -1016,13 +1045,12 @@ fn offer_success() { } // Сreating a new type of collection let name_simple_nft = "Simple NFT".to_string(); - let res = add_new_collection( - &marketplace, + marketplace.add_new_collection( ADMINS[0], nft_collection_code_id.into_bytes().into(), name_simple_nft.clone(), + None, ); - assert!(!res.main_failed()); let state_reply = marketplace .read_state(StateQuery::CollectionsInfo) .expect("Unexpected invalid state."); @@ -1035,14 +1063,14 @@ fn offer_success() { // Create collection let royalty = 1_000; let init_nft_payload = get_init_nft_payload(USERS[0].into(), royalty, Some(3), 0, None); - - let res = create_collection( - &marketplace, + sys.mint_to(USERS[0], 100_000_000_000_000); + marketplace.create_collection( USERS[0], name_simple_nft.clone(), init_nft_payload.encode(), + 10_000_000_000_000, + None, ); - assert!(!res.main_failed()); let state_reply = marketplace .read_state(StateQuery::AllCollections) .expect("Unexpected invalid state."); @@ -1059,8 +1087,7 @@ fn offer_success() { let nft_collection = sys.get_program(address_nft_2); // Successful mint NFT in the new collection - let res = nft_collection.send(USERS[1], nft_io::NftAction::Mint); - assert!(!res.main_failed()); + marketplace.mint(USERS[1], address_nft, None); // Successful approve NFT in the collection let addres_marketplace: [u8; 32] = marketplace.id().into(); @@ -1075,8 +1102,7 @@ fn offer_success() { let offer_price = 150_000_000_000_000; sys.mint_to(USERS[2], offer_price); - let res = create_offer(&marketplace, USERS[2], address_nft, 0, offer_price); - assert!(!res.main_failed()); + marketplace.create_offer(USERS[2], address_nft, 0, offer_price, None); let state_reply = marketplace .read_state(StateQuery::All) @@ -1088,8 +1114,7 @@ fn offer_success() { assert_eq!(offer_price, state.offers[0].1); } - let res = accept_offer(&marketplace, USERS[1], address_nft, 0, USERS[2].into()); - assert!(!res.main_failed()); + marketplace.accept_offer(USERS[1], address_nft, 0, USERS[2].into(), None); let state_reply = marketplace .read_state(StateQuery::All) .expect("Unexpected invalid state."); @@ -1099,17 +1124,31 @@ fn offer_success() { } let percent_to_collection_owner = offer_price * royalty as u128 / 10_000; + let percent_to_marketplace = offer_price * 200 as u128 / 10_000; + sys.claim_value_from_mailbox(USERS[1]); let balance = sys.balance_of(USERS[1]); assert_eq!( balance, - offer_price - percent_to_collection_owner, + offer_price - percent_to_collection_owner - percent_to_marketplace, "Wrong balance" ); + let old_balance = sys.balance_of(USERS[0]); sys.claim_value_from_mailbox(USERS[0]); let balance = sys.balance_of(USERS[0]); - assert_eq!(balance, percent_to_collection_owner, "Wrong balance"); + assert_eq!( + balance - old_balance, + percent_to_collection_owner, + "Wrong balance" + ); + + let balance = sys.balance_of(1); + assert_eq!( + balance, + percent_to_marketplace + 10_000_000_000_000, + "Wrong balance" + ); let state_reply = nft_collection .read_state(StateQueryNft::All) @@ -1126,8 +1165,7 @@ fn offer_success() { #[test] fn offer_failures() { let sys = utils::initialize_system(); - init_marketplace(&sys); - let marketplace = sys.get_program(1); + let marketplace = Program::init_marketplace(&sys); let nft_collection_code_id = sys.submit_code("target/wasm32-unknown-unknown/debug/nft.opt.wasm"); @@ -1140,13 +1178,12 @@ fn offer_failures() { } // Сreating a new type of collection let name_simple_nft = "Simple NFT".to_string(); - let res = add_new_collection( - &marketplace, + marketplace.add_new_collection( ADMINS[0], nft_collection_code_id.into_bytes().into(), name_simple_nft.clone(), + None, ); - assert!(!res.main_failed()); let state_reply = marketplace .read_state(StateQuery::CollectionsInfo) .expect("Unexpected invalid state."); @@ -1160,13 +1197,16 @@ fn offer_failures() { let royalty = 1_000; let init_nft_payload = get_init_nft_payload(USERS[0].into(), royalty, Some(3), 0, None); - let res = create_collection( - &marketplace, + sys.mint_to(USERS[0], 100_000_000_000_000); + marketplace.create_collection( USERS[0], name_simple_nft.clone(), init_nft_payload.encode(), + 10_000_000_000_000, + None, ); - assert!(!res.main_failed()); + let balance = sys.balance_of(1); + assert_eq!(balance, 10_000_000_000_000, "Wrong balance"); let state_reply = marketplace .read_state(StateQuery::AllCollections) .expect("Unexpected invalid state."); @@ -1185,20 +1225,32 @@ fn offer_failures() { // NonFungibleToken: token does not exist let offer_price = 150_000_000_000_000; sys.mint_to(USERS[2], offer_price); - let res = create_offer(&marketplace, USERS[2], address_nft, 0, offer_price); - assert!(!res.main_failed()); - check_marketplace_error(USERS[2], &res, NftMarketplaceError::ErrorFromCollection); + marketplace.create_offer( + USERS[2], + address_nft, + 0, + offer_price, + Some(NftMarketplaceError::ErrorFromCollection), + ); + + sys.claim_value_from_mailbox(USERS[2]); + let balance = sys.balance_of(USERS[2]); + assert_eq!(balance, offer_price, "Wrong balance"); // wrong collection address - let offer_price = 150_000_000_000_000; - sys.mint_to(USERS[2], offer_price); - let res = create_offer(&marketplace, USERS[2], 1.into(), 0, offer_price); - assert!(!res.main_failed()); - check_marketplace_error(USERS[2], &res, NftMarketplaceError::WrongCollectionAddress); + marketplace.create_offer( + USERS[2], + 1.into(), + 0, + offer_price, + Some(NftMarketplaceError::WrongCollectionAddress), + ); + sys.claim_value_from_mailbox(USERS[2]); + let balance = sys.balance_of(USERS[2]); + assert_eq!(balance, offer_price, "Wrong balance"); // Successful mint NFT in the new collection - let res = nft_collection.send(USERS[1], nft_io::NftAction::Mint); - assert!(!res.main_failed()); + marketplace.mint(USERS[1], address_nft, None); // Successful approve NFT in the collection let addres_marketplace: [u8; 32] = marketplace.id().into(); @@ -1213,8 +1265,7 @@ fn offer_failures() { let price = 150_000_000_000_000; - let res = sale(&marketplace, USERS[1], address_nft, 0, price); - assert!(!res.main_failed()); + marketplace.sale(USERS[1], address_nft, 0, price, None); let state_reply = marketplace .read_state(StateQuery::All) @@ -1225,8 +1276,7 @@ fn offer_failures() { let offer_price = 150_000_000_000_000; sys.mint_to(USERS[2], offer_price); - let res = create_offer(&marketplace, USERS[2], address_nft, 0, offer_price); - assert!(!res.main_failed()); + marketplace.create_offer(USERS[2], address_nft, 0, offer_price, None); let state_reply = marketplace .read_state(StateQuery::All) @@ -1238,16 +1288,14 @@ fn offer_failures() { assert_eq!(offer_price, state.offers[0].1); } - let res = accept_offer(&marketplace, USERS[1], address_nft, 0, USERS[2].into()); - let result = &res.decoded_log::>(); - println!("RES: {:?}", result); - check_marketplace_error(USERS[1], &res, NftMarketplaceError::AlreadyOnSale); - assert!(!res.main_failed()); - - let res = cancel_sale(&marketplace, USERS[1], address_nft, 0); - let result = &res.decoded_log::>(); - println!("RES: {:?}", result); - assert!(!res.main_failed()); + marketplace.accept_offer( + USERS[1], + address_nft, + 0, + USERS[2].into(), + Some(NftMarketplaceError::AlreadyOnSale), + ); + marketplace.cancel_sale(USERS[1], address_nft, 0, None); let res = nft_collection.send( USERS[1], @@ -1258,23 +1306,17 @@ fn offer_failures() { ); assert!(!res.main_failed()); - let duration_ms = 10_000; - let res = create_auction( - &marketplace, + let duration = 10; + marketplace.create_auction(USERS[1], address_nft, 0, 10_300_000_000_000, duration, None); + + marketplace.accept_offer( USERS[1], address_nft, 0, - 10_000_000_000_000, - duration_ms, + USERS[2].into(), + Some(NftMarketplaceError::AlreadyOnAuction), ); - assert!(!res.main_failed()); - let res = accept_offer(&marketplace, USERS[1], address_nft, 0, USERS[2].into()); - check_marketplace_error(USERS[1], &res, NftMarketplaceError::AlreadyOnAuction); - - assert!(!res.main_failed()); - - let res = cancel_auction(&marketplace, USERS[1], address_nft, 0); - assert!(!res.main_failed()); + marketplace.cancel_auction(USERS[1], address_nft, 0, None); let res = nft_collection.send( USERS[1], @@ -1285,10 +1327,7 @@ fn offer_failures() { ); assert!(!res.main_failed()); - let res = accept_offer(&marketplace, USERS[1], address_nft, 0, USERS[2].into()); - let result = &res.decoded_log::>(); - println!("RES: {:?}", result); - assert!(!res.main_failed()); + marketplace.accept_offer(USERS[1], address_nft, 0, USERS[2].into(), None); let state_reply = marketplace .read_state(StateQuery::All) @@ -1299,17 +1338,30 @@ fn offer_failures() { } let percent_to_collection_owner = offer_price * royalty as u128 / 10_000; + let percent_to_marketplace = offer_price * 200 as u128 / 10_000; sys.claim_value_from_mailbox(USERS[1]); let balance = sys.balance_of(USERS[1]); assert_eq!( balance, - offer_price - percent_to_collection_owner, + offer_price - percent_to_collection_owner - percent_to_marketplace, "Wrong balance" ); + let old_balance = sys.balance_of(USERS[0]); sys.claim_value_from_mailbox(USERS[0]); let balance = sys.balance_of(USERS[0]); - assert_eq!(balance, percent_to_collection_owner, "Wrong balance"); + assert_eq!( + balance - old_balance, + percent_to_collection_owner, + "Wrong balance" + ); + + let balance = sys.balance_of(1); + assert_eq!( + balance, + percent_to_marketplace + 10_000_000_000_000, + "Wrong balance" + ); let state_reply = nft_collection .read_state(StateQueryNft::All) diff --git a/tests/test_nft.rs b/tests/test_nft.rs index 4b5777d..1aa15a5 100644 --- a/tests/test_nft.rs +++ b/tests/test_nft.rs @@ -3,7 +3,7 @@ use utils::prelude::*; mod utils; use gtest::Program; use nft_io::{ - Action, AdditionalLinks, Config, ImageData, NftAction, NftError, NftEvent, NftInit, NftState, + Action, AdditionalLinks, Config, ImageData, NftAction, NftError, NftInit, NftState, StateQuery as StateQueryNft, StateReply as StateReplyNft, }; use nft_marketplace_io::*; @@ -13,8 +13,7 @@ const USERS: &[u64] = &[5, 6, 7, 8]; #[test] fn successful_basics() { let sys = utils::initialize_system(); - init_marketplace(&sys); - let marketplace = sys.get_program(1); + let marketplace = Program::init_marketplace(&sys); let nft_collection_code_id = sys.submit_code("target/wasm32-unknown-unknown/debug/nft.opt.wasm"); @@ -27,13 +26,13 @@ fn successful_basics() { } // Successful addition of a new collection let name_simple_nft = "Simple NFT".to_string(); - let res = add_new_collection( - &marketplace, + marketplace.add_new_collection( ADMINS[0], nft_collection_code_id.into_bytes().into(), name_simple_nft.clone(), + None, ); - assert!(!res.main_failed()); + let state_reply = marketplace .read_state(StateQuery::CollectionsInfo) .expect("Unexpected invalid state."); @@ -43,16 +42,15 @@ fn successful_basics() { } // Successful creation of a new collection let init_nft_payload = get_init_nft_payload(USERS[0].into(), 0, Some(3), 0, None); - - let res = create_collection( - &marketplace, + sys.mint_to(USERS[0], 100_000_000_000_000); + marketplace.create_collection( USERS[0], name_simple_nft.clone(), init_nft_payload.encode(), + 10_000_000_000_000, + None, ); - assert!(!res.main_failed()); - let result = &res.decoded_log::>(); - println!("RES: {:?}", result); + let state_reply = marketplace .read_state(StateQuery::AllCollections) .expect("Unexpected invalid state."); @@ -65,8 +63,8 @@ fn successful_basics() { 0.into() }; - let address_nft: [u8; 32] = address_nft.into(); - let nft_collection = sys.get_program(address_nft); + let address_nft_list: [u8; 32] = address_nft.into(); + let nft_collection = sys.get_program(address_nft_list); let state_reply = nft_collection .read_state(StateQueryNft::All) .expect("Unexpected invalid state."); @@ -100,8 +98,7 @@ fn successful_basics() { } // Successful mint NFT in the new collection - let res = nft_collection.send(USERS[1], NftAction::Mint); - assert!(!res.main_failed()); + marketplace.mint(USERS[1], address_nft, None); let state_reply = nft_collection .read_state(StateQueryNft::All) @@ -174,20 +171,14 @@ fn successful_basics() { } // Check limit of mint = 3 - let res = nft_collection.send(USERS[3], NftAction::Mint); - assert!(!res.main_failed()); - let res = nft_collection.send(USERS[3], NftAction::Mint); - assert!(!res.main_failed()); - let res = nft_collection.send(USERS[3], NftAction::Mint); - assert!(!res.main_failed()); - let res = nft_collection.send(USERS[3], NftAction::Mint); - assert!(!res.main_failed()); - let result = &res.decoded_log::>()[0]; - println!("RES {:?}", result); - assert!(res.contains(&( + marketplace.mint(USERS[3], address_nft, None); + marketplace.mint(USERS[3], address_nft, None); + marketplace.mint(USERS[3], address_nft, None); + marketplace.mint( USERS[3], - Err::(NftError::ExhaustedLimit).encode() - ))); + address_nft, + Some(NftMarketplaceError::ErrorFromCollection), + ); // Successful Expand NFT in the collection let img_data = ImageData { @@ -220,41 +211,46 @@ fn successful_basics() { #[test] fn failures() { let sys = utils::initialize_system(); - init_marketplace(&sys); - let marketplace = sys.get_program(1); + let marketplace = Program::init_marketplace(&sys); let nft_collection_code_id = sys.submit_code("target/wasm32-unknown-unknown/debug/nft.opt.wasm"); let name_simple_nft = "Simple NFT".to_string(); - let res = add_new_collection( - &marketplace, + marketplace.add_new_collection( ADMINS[0], nft_collection_code_id.into_bytes().into(), name_simple_nft.clone(), + None, ); - assert!(!res.main_failed()); // The mint limit must be greater than zero let mut init_nft_payload = get_init_nft_payload(USERS[0].into(), 0, Some(0), 0, None); - - let res = create_collection( - &marketplace, + sys.mint_to(USERS[0], 100_000_000_000_000); + marketplace.create_collection( USERS[0], name_simple_nft.clone(), init_nft_payload.encode(), + 10_000_000_000_000, + Some(NftMarketplaceError::CreationError), ); - assert!(res.main_failed()); + sys.claim_value_from_mailbox(USERS[0]); + let balance = sys.balance_of(USERS[0]); + assert_eq!(balance, 100_000_000_000_000, "Wrong balance"); // There must be at least one link to create a collection init_nft_payload.config.user_mint_limit = 4.into(); init_nft_payload.img_links_and_data = vec![]; - let res = create_collection( - &marketplace, + marketplace.create_collection( USERS[0], name_simple_nft.clone(), init_nft_payload.encode(), + 10_000_000_000_000, + Some(NftMarketplaceError::CreationError), ); - assert!(res.main_failed()); + + sys.claim_value_from_mailbox(USERS[0]); + let balance = sys.balance_of(USERS[0]); + assert_eq!(balance, 100_000_000_000_000, "Wrong balance"); // Limit of copies value is equal to 0 let img_data = ImageData { @@ -262,13 +258,16 @@ fn failures() { auto_changing_rules: None, }; init_nft_payload.img_links_and_data = vec![("Img-0".to_owned(), img_data.clone())]; - let res = create_collection( - &marketplace, + marketplace.create_collection( USERS[0], name_simple_nft.clone(), init_nft_payload.encode(), + 10_000_000_000_000, + Some(NftMarketplaceError::CreationError), ); - assert!(res.main_failed()); + sys.claim_value_from_mailbox(USERS[0]); + let balance = sys.balance_of(USERS[0]); + assert_eq!(balance, 100_000_000_000_000, "Wrong balance"); let img_data = ImageData { limit_copies: Some(1), @@ -279,13 +278,13 @@ fn failures() { .collect(); init_nft_payload.img_links_and_data = img_links_and_data; - let res = create_collection( - &marketplace, + marketplace.create_collection( USERS[0], name_simple_nft.clone(), init_nft_payload.encode(), + 10_000_000_000_000, + None, ); - assert!(!res.main_failed()); let state_reply = marketplace .read_state(StateQuery::AllCollections) @@ -299,23 +298,23 @@ fn failures() { 0.into() }; - let address_nft: [u8; 32] = address_nft.into(); - let nft_collection = sys.get_program(address_nft); + let address_nft_list: [u8; 32] = address_nft.into(); + let nft_collection = sys.get_program(address_nft_list); for _ in 0..4 { - let res = nft_collection.send(USERS[1], NftAction::Mint); - assert!(!res.main_failed()); + marketplace.mint(USERS[1], address_nft, None); } - let res = nft_collection.send(USERS[1], NftAction::Mint); - check_nft_error(USERS[1], &res, NftError::ExhaustedLimit); - - assert!(!res.main_failed()); - - let res = nft_collection.send(USERS[2], NftAction::Mint); - assert!(!res.main_failed()); - let res = nft_collection.send(USERS[2], NftAction::Mint); - check_nft_error(USERS[2], &res, NftError::AllTokensMinted); - assert!(!res.main_failed()); + marketplace.mint( + USERS[1], + address_nft, + Some(NftMarketplaceError::ErrorFromCollection), + ); + marketplace.mint(USERS[2], address_nft, None); + marketplace.mint( + USERS[2], + address_nft, + Some(NftMarketplaceError::ErrorFromCollection), + ); let config = Config { name: "User Collection".to_string(), @@ -352,8 +351,8 @@ fn failures() { }, ); assert!(!res.main_failed()); - let res = nft_collection.send(USERS[2], NftAction::Mint); - assert!(!res.main_failed()); + marketplace.mint(USERS[2], address_nft, None); + let res = nft_collection.send( USERS[2], NftAction::Approve { @@ -397,8 +396,7 @@ fn failures() { #[test] fn check_auto_changing_rules() { let sys = utils::initialize_system(); - init_marketplace(&sys); - let marketplace = sys.get_program(1); + let marketplace = Program::init_marketplace(&sys); let nft_collection_code_id = sys.submit_code("target/wasm32-unknown-unknown/debug/nft.opt.wasm"); @@ -411,13 +409,13 @@ fn check_auto_changing_rules() { } // Successful addition of a new collection let name_simple_nft = "Simple NFT".to_string(); - let res = add_new_collection( - &marketplace, + marketplace.add_new_collection( ADMINS[0], nft_collection_code_id.into_bytes().into(), name_simple_nft.clone(), + None, ); - assert!(!res.main_failed()); + let state_reply = marketplace .read_state(StateQuery::CollectionsInfo) .expect("Unexpected invalid state."); @@ -462,16 +460,16 @@ fn check_auto_changing_rules() { }, img_links_and_data, permission_to_mint: None, + fee_per_uploaded_file: 257_142_857_100, }; - let res = create_collection( - &marketplace, + sys.mint_to(USERS[0], 100_000_000_000_000); + marketplace.create_collection( USERS[0], name_simple_nft.clone(), init_nft_payload.encode(), + 10_000_000_000_000, + None, ); - assert!(!res.main_failed()); - let result = &res.decoded_log::>(); - println!("RES: {:?}", result); let state_reply = marketplace .read_state(StateQuery::AllCollections) .expect("Unexpected invalid state."); @@ -484,8 +482,8 @@ fn check_auto_changing_rules() { 0.into() }; - let address_nft: [u8; 32] = address_nft.into(); - let nft_collection = sys.get_program(address_nft); + let address_nft_list: [u8; 32] = address_nft.into(); + let nft_collection = sys.get_program(address_nft_list); let state_reply = nft_collection .read_state(StateQueryNft::All) .expect("Unexpected invalid state."); @@ -495,8 +493,7 @@ fn check_auto_changing_rules() { } // Successful mint NFT in the new collection - let res = nft_collection.send(USERS[1], NftAction::Mint); - assert!(!res.main_failed()); + marketplace.mint(USERS[1], address_nft, None); let state_reply = nft_collection .read_state(StateQueryNft::All) @@ -534,33 +531,30 @@ fn check_auto_changing_rules() { #[test] fn check_transferable() { let sys = utils::initialize_system(); - init_marketplace(&sys); - let marketplace = sys.get_program(1); + let marketplace = Program::init_marketplace(&sys); let nft_collection_code_id = sys.submit_code("target/wasm32-unknown-unknown/debug/nft.opt.wasm"); // Successful addition of a new collection let name_simple_nft = "Simple NFT".to_string(); - let res = add_new_collection( - &marketplace, + marketplace.add_new_collection( ADMINS[0], nft_collection_code_id.into_bytes().into(), name_simple_nft.clone(), + None, ); - assert!(!res.main_failed()); let mut init_nft_payload = get_init_nft_payload(USERS[0].into(), 0, Some(3), 0, None); let transferable_time = 9_000; init_nft_payload.config.transferable = Some(transferable_time); - let res = create_collection( - &marketplace, + sys.mint_to(USERS[0], 100_000_000_000_000); + marketplace.create_collection( USERS[0], name_simple_nft.clone(), init_nft_payload.encode(), + 10_000_000_000_000, + None, ); - assert!(!res.main_failed()); - let result = &res.decoded_log::>(); - println!("RES: {:?}", result); let state_reply = marketplace .read_state(StateQuery::AllCollections) .expect("Unexpected invalid state."); @@ -573,8 +567,8 @@ fn check_transferable() { 0.into() }; - let address_nft: [u8; 32] = address_nft.into(); - let nft_collection = sys.get_program(address_nft); + let address_nft_list: [u8; 32] = address_nft.into(); + let nft_collection = sys.get_program(address_nft_list); let state_reply = nft_collection .read_state(StateQueryNft::All) .expect("Unexpected invalid state."); @@ -584,8 +578,7 @@ fn check_transferable() { } // Successful mint NFT in the new collection - let res = nft_collection.send(USERS[1], NftAction::Mint); - assert!(!res.main_failed()); + marketplace.mint(USERS[1], address_nft, None); // Transfer NFT in the collection let res = nft_collection.send( @@ -622,8 +615,7 @@ fn check_transferable() { #[test] fn check_payment_for_mint() { let sys = utils::initialize_system(); - init_marketplace(&sys); - let marketplace = sys.get_program(1); + let marketplace = Program::init_marketplace(&sys); let nft_collection_code_id = sys.submit_code("target/wasm32-unknown-unknown/debug/nft.opt.wasm"); @@ -636,13 +628,13 @@ fn check_payment_for_mint() { } // Successful addition of a new collection let name_simple_nft = "Simple NFT".to_string(); - let res = add_new_collection( - &marketplace, + marketplace.add_new_collection( ADMINS[0], nft_collection_code_id.into_bytes().into(), name_simple_nft.clone(), + None, ); - assert!(!res.main_failed()); + let state_reply = marketplace .read_state(StateQuery::CollectionsInfo) .expect("Unexpected invalid state."); @@ -655,27 +647,29 @@ fn check_payment_for_mint() { let mut init_nft_payload = get_init_nft_payload(USERS[0].into(), 0, Some(3), 0, None); let payment_for_mint = 9_000_000_000_000; init_nft_payload.config.payment_for_mint = payment_for_mint; - let res = create_collection( - &marketplace, + sys.mint_to(USERS[0], 100_000_000_000_000); + marketplace.create_collection( USERS[0], name_simple_nft.clone(), init_nft_payload.encode(), + 10_000_000_000_000, + Some(NftMarketplaceError::CreationError), ); - assert!(res.main_failed()); + sys.claim_value_from_mailbox(USERS[0]); + let balance = sys.balance_of(USERS[0]); + assert_eq!(balance, 100_000_000_000_000, "Wrong balance"); // Successful creation of a new collection let payment_for_mint = 11_000_000_000_000; init_nft_payload.config.payment_for_mint = payment_for_mint; - let res = create_collection( - &marketplace, + marketplace.create_collection( USERS[0], name_simple_nft.clone(), init_nft_payload.encode(), + 10_000_000_000_000, + None, ); - assert!(!res.main_failed()); - let result = &res.decoded_log::>(); - println!("RES: {:?}", result); let state_reply = marketplace .read_state(StateQuery::AllCollections) .expect("Unexpected invalid state."); @@ -688,8 +682,8 @@ fn check_payment_for_mint() { 0.into() }; - let address_nft: [u8; 32] = address_nft.into(); - let nft_collection = sys.get_program(address_nft); + let address_nft_list: [u8; 32] = address_nft.into(); + let nft_collection = sys.get_program(address_nft_list); let state_reply = nft_collection .read_state(StateQueryNft::All) .expect("Unexpected invalid state."); @@ -699,23 +693,43 @@ fn check_payment_for_mint() { } // Successful mint NFT in the new collection - sys.mint_to(USERS[1], 2 * payment_for_mint); - let res = nft_collection.send_with_value(USERS[1], NftAction::Mint, payment_for_mint); + sys.mint_to(USERS[1], 3 * payment_for_mint); + let res = marketplace.send_with_value( + USERS[1], + NftMarketplaceAction::Mint { + collection_address: address_nft, + }, + payment_for_mint + payment_for_mint * 200 / 10_000, + ); assert!(!res.main_failed()); + let old_balance = sys.balance_of(USERS[0]); sys.claim_value_from_mailbox(USERS[0]); let balance = sys.balance_of(USERS[0]); - assert_eq!(balance, payment_for_mint, "Wrong balance"); - let res = nft_collection.send_with_value(USERS[1], NftAction::Mint, payment_for_mint - 1); + assert_eq!(balance - old_balance, payment_for_mint, "Wrong balance"); + let res = marketplace.send_with_value( + USERS[1], + NftMarketplaceAction::Mint { + collection_address: address_nft, + }, + payment_for_mint, + ); + + let result = &res.decoded_log::>(); + println!("RES: {:?}", result); + assert!(!res.main_failed()); - check_nft_error(USERS[1], &res, NftError::WrongValue); + + assert!(res.contains(&( + USERS[1], + Err::(NftMarketplaceError::WrongValue).encode() + ))); } #[test] fn permission_to_mint() { let sys = utils::initialize_system(); - init_marketplace(&sys); - let marketplace = sys.get_program(1); + let marketplace = Program::init_marketplace(&sys); let nft_collection_code_id = sys.submit_code("target/wasm32-unknown-unknown/debug/nft.opt.wasm"); @@ -728,13 +742,12 @@ fn permission_to_mint() { } // Successful addition of a new collection let name_simple_nft = "Simple NFT".to_string(); - let res = add_new_collection( - &marketplace, + marketplace.add_new_collection( ADMINS[0], nft_collection_code_id.into_bytes().into(), name_simple_nft.clone(), + None, ); - assert!(!res.main_failed()); let state_reply = marketplace .read_state(StateQuery::CollectionsInfo) .expect("Unexpected invalid state."); @@ -744,14 +757,14 @@ fn permission_to_mint() { } // Successful creation of a new collection let init_nft_payload = get_init_nft_payload(USERS[0].into(), 0, Some(3), 0, Some(vec![])); - - let res = create_collection( - &marketplace, + sys.mint_to(USERS[0], 100_000_000_000_000); + marketplace.create_collection( USERS[0], name_simple_nft.clone(), init_nft_payload.encode(), + 10_000_000_000_000, + None, ); - assert!(!res.main_failed()); let state_reply = marketplace .read_state(StateQuery::AllCollections) @@ -765,8 +778,8 @@ fn permission_to_mint() { 0.into() }; - let address_nft: [u8; 32] = address_nft.into(); - let nft_collection = sys.get_program(address_nft); + let address_nft_list: [u8; 32] = address_nft.into(); + let nft_collection = sys.get_program(address_nft_list); let state_reply = nft_collection .read_state(StateQueryNft::All) .expect("Unexpected invalid state."); @@ -775,14 +788,14 @@ fn permission_to_mint() { } // Succes mint NFT from admin - let res = nft_collection.send(USERS[1], NftAction::Mint); - assert!(!res.main_failed()); + marketplace.mint(USERS[0], address_nft, None); // Fail mint NFT from user - let res = nft_collection.send(USERS[1], NftAction::Mint); - assert!(!res.main_failed()); - - check_nft_error(USERS[1], &res, NftError::AccessDenied); + marketplace.mint( + USERS[1], + address_nft, + Some(NftMarketplaceError::ErrorFromCollection), + ); let res = nft_collection.send( USERS[0], @@ -793,8 +806,7 @@ fn permission_to_mint() { assert!(!res.main_failed()); // Success mint - let res = nft_collection.send(USERS[1], NftAction::Mint); - assert!(!res.main_failed()); + marketplace.mint(USERS[1], address_nft, None); let state_reply = nft_collection .read_state(StateQueryNft::All) @@ -802,9 +814,9 @@ fn permission_to_mint() { if let StateReplyNft::All(state) = state_reply { println!("Collection NFT info: {:?}", state); assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); - let (owner, token_id) = state.owners.get(0).expect("Can't be None"); + let (owner, token_id) = state.owners.get(1).expect("Can't be None"); assert_eq!(*owner, USERS[1].into(), "Wrong owner"); - assert_eq!(*token_id, vec![0], "Wrong token id"); + assert_eq!(*token_id, vec![1], "Wrong token id"); } let res = nft_collection.send( USERS[0], @@ -815,10 +827,11 @@ fn permission_to_mint() { assert!(!res.main_failed()); // Fail mint NFT in the new collection - let res = nft_collection.send(USERS[1], NftAction::Mint); - assert!(!res.main_failed()); - - check_nft_error(USERS[1], &res, NftError::AccessDenied); + marketplace.mint( + USERS[1], + address_nft, + Some(NftMarketplaceError::ErrorFromCollection), + ); } fn get_state(nft_collection: &Program) -> Option { @@ -839,9 +852,9 @@ fn get_state(nft_collection: &Program) -> Option { // #[test] // fn check() { -// let input = "00033c5573657220436f6c6c656374696f6e3c5573657220436f6c6c656374696f6e04107461673144436f6c6c656374696f6e2062616e6e65723c436f6c6c656374696f6e206c6f676f010300000000000000000000000000000000000000000000010000000000000000010000000000000000"; +// let input = "000080ee3600000000008c9de8de3b0000000000000000000000c800c800"; // let decoded = hex::decode(input).expect("Decoding failed"); // let mut res: &[u8] = &decoded; -// let result = Result::::decode(&mut res).ok(); +// let result = Result::::decode(&mut res).ok(); // println!("RES: {:?}", result); // } diff --git a/tests/utils/mod.rs b/tests/utils/mod.rs index 612ec2a..63095e3 100644 --- a/tests/utils/mod.rs +++ b/tests/utils/mod.rs @@ -12,234 +12,518 @@ pub use common::initialize_system; pub const ADMINS: [u64; 2] = [123, 321]; -pub fn init_marketplace(sys: &System) { - let marketplace = Program::current(sys); - let init_payload = NftMarketplaceInit { - gas_for_creation: 1_000_000_000_000_000, - gas_for_transfer_token: 5_000_000_000, - gas_for_close_auction: 10_000_000_000, - gas_for_delete_collection: 5_000_000_000, - gas_for_get_token_info: 5_000_000_000, - time_between_create_collections: 3_600_000, // 1 hour in milliseconds - minimum_transfer_value: 10_000_000_000_000, - ms_in_block: 3_000, - }; - let res = marketplace.send(ADMINS[0], init_payload); - assert!(!res.main_failed()); +pub trait NftMarketplace { + fn init_marketplace(sys: &System) -> Program<'_>; + fn add_new_collection( + &self, + admin: u64, + code_id: CodeId, + type_name: String, + error: Option, + ); + fn create_collection( + &self, + user: u64, + type_name: String, + payload: Vec, + value: u128, + error: Option, + ); + fn mint(&self, user: u64, collection_address: ActorId, error: Option); + fn sale( + &self, + user: u64, + collection_address: ActorId, + token_id: u64, + price: u128, + error: Option, + ); + fn cancel_sale( + &self, + user: u64, + collection_address: ActorId, + token_id: u64, + error: Option, + ); + fn create_auction( + &self, + user: u64, + collection_address: ActorId, + token_id: u64, + min_price: u128, + duration: u32, + error: Option, + ); + fn cancel_auction( + &self, + user: u64, + collection_address: ActorId, + token_id: u64, + error: Option, + ); + fn add_bid( + &self, + user: u64, + collection_address: ActorId, + token_id: u64, + price: u128, + error: Option, + ); + fn buy( + &self, + user: u64, + collection_address: ActorId, + token_id: u64, + price: u128, + error: Option, + ); + fn create_offer( + &self, + user: u64, + collection_address: ActorId, + token_id: u64, + value: u128, + error: Option, + ); + fn accept_offer( + &self, + user: u64, + collection_address: ActorId, + token_id: u64, + creator: ActorId, + error: Option, + ); + fn add_admin(&self, admin: u64, users: Vec, error: Option); + fn update_config( + &self, + admin: u64, + gas_for_creation: Option, + gas_for_mint: Option, + gas_for_transfer_token: Option, + gas_for_close_auction: Option, + gas_for_delete_collection: Option, + gas_for_get_info: Option, + time_between_create_collections: Option, + fee_per_uploaded_file: Option, + royalty_to_marketplace_for_trade: Option, + royalty_to_marketplace_for_mint: Option, + minimum_transfer_value: Option, + ms_in_block: Option, + error: Option, + ); + fn delete_collection( + &self, + admin: u64, + collection_address: ActorId, + error: Option, + ); + fn delete_admin(&self, admin: u64, user: ActorId, error: Option); } -pub fn add_new_collection( - marketplace: &Program, - admin: u64, - code_id: CodeId, - type_name: String, -) -> RunResult { - let type_description = String::from("My Collection"); - let meta_link = String::from("My Meta"); +impl NftMarketplace for Program<'_> { + fn init_marketplace(sys: &System) -> Program<'_> { + let marketplace = Program::current(sys); + let init_payload = NftMarketplaceInit { + gas_for_creation: 1_000_000_000_000_000, + gas_for_mint: 100_000_000_000, + gas_for_transfer_token: 5_000_000_000, + gas_for_close_auction: 10_000_000_000, + gas_for_delete_collection: 5_000_000_000, + gas_for_get_info: 5_000_000_000, + time_between_create_collections: 3_600_000, // 1 hour in milliseconds + fee_per_uploaded_file: 257_142_857_100, + royalty_to_marketplace_for_trade: 200, + royalty_to_marketplace_for_mint: 200, + minimum_transfer_value: 10_300_000_000_000, // because roylty to marketplace + ms_in_block: 3_000, + }; + let res = marketplace.send(ADMINS[0], init_payload); + assert!(!res.main_failed()); + marketplace + } + fn add_new_collection( + &self, + admin: u64, + code_id: CodeId, + type_name: String, + error: Option, + ) { + let type_description = String::from("My Collection"); + let meta_link = String::from("My Meta"); - marketplace.send( - admin, - NftMarketplaceAction::AddNewCollection { - code_id, - meta_link, - type_name, - type_description, - }, - ) -} + let res = self.send( + admin, + NftMarketplaceAction::AddNewCollection { + code_id, + meta_link: meta_link.clone(), + type_name: type_name.clone(), + type_description: type_description.clone(), + }, + ); + assert!(!res.main_failed()); -pub fn create_collection( - marketplace: &Program, - user: u64, - type_name: String, - payload: Vec, -) -> RunResult { - marketplace.send( - user, - NftMarketplaceAction::CreateCollection { type_name, payload }, - ) -} + let reply = if let Some(error) = error { + Err(error) + } else { + Ok(NftMarketplaceEvent::NewCollectionAdded { + code_id, + meta_link, + type_name, + type_description, + }) + }; + assert!(res.contains(&(admin, reply.encode()))); + } + fn create_collection( + &self, + user: u64, + type_name: String, + payload: Vec, + value: u128, + error: Option, + ) { + let res = self.send_with_value( + user, + NftMarketplaceAction::CreateCollection { type_name, payload }, + value, + ); + assert!(!res.main_failed()); + if let Some(error) = error { + let reply = Err::(error); + assert!(res.contains(&(user, reply.encode()))); + } + } + fn mint(&self, user: u64, collection_address: ActorId, error: Option) { + let res = self.send(user, NftMarketplaceAction::Mint { collection_address }); + assert!(!res.main_failed()); -pub fn sale( - marketplace: &Program, - user: u64, - collection_address: ActorId, - token_id: u64, - price: u128, -) -> RunResult { - marketplace.send( - user, - NftMarketplaceAction::SaleNft { - collection_address, - token_id, + let reply = if let Some(error) = error { + Err(error) + } else { + Ok(NftMarketplaceEvent::Minted { + collection_address, + minter: user.into(), + }) + }; + assert!(res.contains(&(user, reply.encode()))); + } + fn sale( + &self, + user: u64, + collection_address: ActorId, + token_id: u64, + price: u128, + error: Option, + ) { + let res = self.send( + user, + NftMarketplaceAction::SaleNft { + collection_address, + token_id, + price, + }, + ); + assert!(!res.main_failed()); + + let reply = if let Some(error) = error { + Err(error) + } else { + Ok(NftMarketplaceEvent::SaleNft { + collection_address, + token_id, + price, + owner: user.into(), + }) + }; + assert!(res.contains(&(user, reply.encode()))); + } + fn cancel_sale( + &self, + user: u64, + collection_address: ActorId, + token_id: u64, + error: Option, + ) { + let res = self.send( + user, + NftMarketplaceAction::CancelSaleNft { + collection_address, + token_id, + }, + ); + assert!(!res.main_failed()); + + let reply = if let Some(error) = error { + Err(error) + } else { + Ok(NftMarketplaceEvent::SaleNftCanceled { + collection_address, + token_id, + }) + }; + assert!(res.contains(&(user, reply.encode()))); + } + fn create_auction( + &self, + user: u64, + collection_address: ActorId, + token_id: u64, + min_price: u128, + duration: u32, + error: Option, + ) { + let res = self.send( + user, + NftMarketplaceAction::CreateAuction { + collection_address, + token_id, + min_price, + duration, + }, + ); + assert!(!res.main_failed()); + let reply = if let Some(error) = error { + Err(error) + } else { + Ok(NftMarketplaceEvent::AuctionCreated { + collection_address, + token_id, + min_price, + duration, + }) + }; + let result = &res.decoded_log::>(); + println!("RES: {:?}", result); + assert!(res.contains(&(user, reply.encode()))); + } + fn cancel_auction( + &self, + user: u64, + collection_address: ActorId, + token_id: u64, + error: Option, + ) { + let res = self.send( + user, + NftMarketplaceAction::CancelAuction { + collection_address, + token_id, + }, + ); + assert!(!res.main_failed()); + let reply = if let Some(error) = error { + Err(error) + } else { + Ok(NftMarketplaceEvent::AuctionCanceled { + collection_address, + token_id, + }) + }; + assert!(res.contains(&(user, reply.encode()))); + } + fn add_bid( + &self, + user: u64, + collection_address: ActorId, + token_id: u64, + price: u128, + error: Option, + ) { + let res = self.send_with_value( + user, + NftMarketplaceAction::AddBid { + collection_address, + token_id, + }, price, - }, - ) -} -pub fn cancel_sale( - marketplace: &Program, - user: u64, - collection_address: ActorId, - token_id: u64, -) -> RunResult { - marketplace.send( - user, - NftMarketplaceAction::CancelSaleNft { - collection_address, - token_id, - }, - ) -} -pub fn create_auction( - marketplace: &Program, - user: u64, - collection_address: ActorId, - token_id: u64, - min_price: u128, - duration_ms: u32, -) -> RunResult { - marketplace.send( - user, - NftMarketplaceAction::CreateAuction { - collection_address, - token_id, - min_price, - duration_ms, - }, - ) -} -pub fn cancel_auction( - marketplace: &Program, - user: u64, - collection_address: ActorId, - token_id: u64, -) -> RunResult { - marketplace.send( - user, - NftMarketplaceAction::CancelAuction { - collection_address, - token_id, - }, - ) -} -pub fn add_bid( - marketplace: &Program, - user: u64, - collection_address: ActorId, - token_id: u64, - price: u128, -) -> RunResult { - marketplace.send_with_value( - user, - NftMarketplaceAction::AddBid { - collection_address, - token_id, - }, - price, - ) -} -pub fn buy( - marketplace: &Program, - user: u64, - collection_address: ActorId, - token_id: u64, - price: u128, -) -> RunResult { - marketplace.send_with_value( - user, - NftMarketplaceAction::BuyNft { - collection_address, - token_id, - }, - price, - ) -} + ); + assert!(!res.main_failed()); + let reply = if let Some(error) = error { + Err(error) + } else { + Ok(NftMarketplaceEvent::BidAdded { + collection_address, + token_id, + current_price: price, + }) + }; + assert!(res.contains(&(user, reply.encode()))); + } + fn buy( + &self, + user: u64, + collection_address: ActorId, + token_id: u64, + price: u128, + error: Option, + ) { + let res = self.send_with_value( + user, + NftMarketplaceAction::BuyNft { + collection_address, + token_id, + }, + price, + ); + assert!(!res.main_failed()); + let reply = if let Some(error) = error { + Err(error) + } else { + Ok(NftMarketplaceEvent::NftSold { + collection_address, + token_id, + price, + current_owner: user.into(), + }) + }; + assert!(res.contains(&(user, reply.encode()))); + } -pub fn create_offer( - marketplace: &Program, - user: u64, - collection_address: ActorId, - token_id: u64, - value: u128, -) -> RunResult { - marketplace.send_with_value( - user, - NftMarketplaceAction::CreateOffer { + fn create_offer( + &self, + user: u64, + collection_address: ActorId, + token_id: u64, + value: u128, + error: Option, + ) { + let res = self.send_with_value( + user, + NftMarketplaceAction::CreateOffer { + collection_address, + token_id, + }, + value, + ); + assert!(!res.main_failed()); + let reply = if let Some(error) = error { + Err(error) + } else { + Ok(NftMarketplaceEvent::OfferCreated { + collection_address, + token_id, + price: value, + }) + }; + assert!(res.contains(&(user, reply.encode()))); + } + + fn accept_offer( + &self, + user: u64, + collection_address: ActorId, + token_id: u64, + creator: ActorId, + error: Option, + ) { + let offer = Offer { collection_address, token_id, - }, - value, - ) -} - -pub fn accept_offer( - marketplace: &Program, - user: u64, - collection_address: ActorId, - token_id: u64, - creator: ActorId, -) -> RunResult { - let offer = Offer { - collection_address, - token_id, - creator, - }; - marketplace.send(user, NftMarketplaceAction::AcceptOffer { offer }) -} - -pub fn add_admin(marketplace: &Program, admin: u64, users: Vec) -> RunResult { - marketplace.send(admin, NftMarketplaceAction::AddAdmins { users }) -} + creator, + }; + let res = self.send( + user, + NftMarketplaceAction::AcceptOffer { + offer: offer.clone(), + }, + ); + assert!(!res.main_failed()); + let reply = if let Some(error) = error { + Err(error) + } else { + Ok(NftMarketplaceEvent::OfferAccepted { offer }) + }; + assert!(res.contains(&(user, reply.encode()))); + } + fn add_admin(&self, admin: u64, users: Vec, error: Option) { + let res = self.send( + admin, + NftMarketplaceAction::AddAdmins { + users: users.clone(), + }, + ); + assert!(!res.main_failed()); + let reply = if let Some(error) = error { + Err(error) + } else { + Ok(NftMarketplaceEvent::AdminsAdded { users }) + }; + assert!(res.contains(&(admin, reply.encode()))); + } + fn update_config( + &self, + admin: u64, + gas_for_creation: Option, + gas_for_mint: Option, + gas_for_transfer_token: Option, + gas_for_close_auction: Option, + gas_for_delete_collection: Option, + gas_for_get_info: Option, + time_between_create_collections: Option, + fee_per_uploaded_file: Option, + royalty_to_marketplace_for_trade: Option, + royalty_to_marketplace_for_mint: Option, + minimum_transfer_value: Option, + ms_in_block: Option, + error: Option, + ) { + let res = self.send( + admin, + NftMarketplaceAction::UpdateConfig { + gas_for_creation, + gas_for_mint, + gas_for_transfer_token, + gas_for_close_auction, + gas_for_delete_collection, + gas_for_get_info, + time_between_create_collections, + fee_per_uploaded_file, + royalty_to_marketplace_for_trade, + royalty_to_marketplace_for_mint, + minimum_transfer_value, + ms_in_block, + }, + ); + assert!(!res.main_failed()); + if let Some(error) = error { + let reply = Err::(error); + assert!(res.contains(&(admin, reply.encode()))); + } + } -pub fn update_config( - marketplace: &Program, - admin: u64, - gas_for_creation: Option, - gas_for_transfer_token: Option, - gas_for_close_auction: Option, - gas_for_delete_collection: Option, - gas_for_get_token_info: Option, - time_between_create_collections: Option, - minimum_transfer_value: Option, - ms_in_block: Option, -) -> RunResult { - marketplace.send( - admin, - NftMarketplaceAction::UpdateConfig { - gas_for_creation, - gas_for_transfer_token, - gas_for_close_auction, - gas_for_delete_collection, - gas_for_get_token_info, - time_between_create_collections, - minimum_transfer_value, - ms_in_block, - }, - ) + fn delete_collection( + &self, + admin: u64, + collection_address: ActorId, + error: Option, + ) { + let res = self.send( + admin, + NftMarketplaceAction::DeleteCollection { collection_address }, + ); + assert!(!res.main_failed()); + let reply = if let Some(error) = error { + Err(error) + } else { + Ok(NftMarketplaceEvent::CollectionDeleted { collection_address }) + }; + assert!(res.contains(&(admin, reply.encode()))); + } + fn delete_admin(&self, admin: u64, user: ActorId, error: Option) { + let res = self.send(admin, NftMarketplaceAction::DeleteAdmin { user }); + assert!(!res.main_failed()); + let reply = if let Some(error) = error { + Err(error) + } else { + Ok(NftMarketplaceEvent::AdminDeleted { user }) + }; + assert!(res.contains(&(admin, reply.encode()))); + } } -pub fn delete_collection( - marketplace: &Program, - admin: u64, - collection_address: ActorId, -) -> RunResult { - marketplace.send( - admin, - NftMarketplaceAction::DeleteCollection { collection_address }, - ) -} -pub fn delete_admin(marketplace: &Program, admin: u64, user: ActorId) -> RunResult { - marketplace.send(admin, NftMarketplaceAction::DeleteAdmin { user }) -} pub fn check_nft_error(from: u64, result: &RunResult, error: NftError) { assert!(result.contains(&(from, Err::(error).encode()))); } -pub fn check_marketplace_error(from: u64, result: &RunResult, error: NftMarketplaceError) { - assert!(result.contains(&( - from, - Err::(error).encode() - ))); -} pub fn get_init_nft_payload( collection_owner: ActorId, @@ -273,6 +557,7 @@ pub fn get_init_nft_payload( }, img_links_and_data, permission_to_mint, + fee_per_uploaded_file: 257_142_857_100, } } // pub fn get_state( diff --git a/tests/utils_gclient/mod.rs b/tests/utils_gclient/mod.rs new file mode 100644 index 0000000..7658b14 --- /dev/null +++ b/tests/utils_gclient/mod.rs @@ -0,0 +1,318 @@ +use gclient::{GearApi, Result}; +use gear_core::ids::{MessageId, ProgramId}; +use gstd::{ActorId, Encode}; +use nft_io::{Config, ImageData, NftInit}; +use nft_io::{NftAction, NftState, StateQuery as StateQueryNft, StateReply as StateReplyNft}; +use nft_marketplace_io::*; + +pub const USERS: &[u64] = &[5, 6, 7, 8]; +pub const USERS_STR: &[&str] = &["//John", "//Mike", "//Dan"]; +pub const ALICE: [u8; 32] = [ + 212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, + 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125, +]; + +pub trait ApiUtils { + fn get_actor_id(&self) -> ActorId; + fn get_specific_actor_id(&self, value: impl AsRef) -> ActorId; +} + +impl ApiUtils for GearApi { + fn get_actor_id(&self) -> ActorId { + ActorId::new( + self.account_id() + .encode() + .try_into() + .expect("Unexpected invalid account id length."), + ) + } + + fn get_specific_actor_id(&self, value: impl AsRef) -> ActorId { + let api_temp = self + .clone() + .with(value) + .expect("Unable to build `GearApi` instance with provided signer."); + api_temp.get_actor_id() + } +} + +pub async fn get_all_state_nft(api: &GearApi, program_id: &ProgramId) -> Option { + let reply = api + .read_state(*program_id, StateQueryNft::All.encode()) + .await + .expect("Unexpected invalid reply."); + if let StateReplyNft::All(state) = reply { + Some(state) + } else { + None + } +} + +pub async fn get_all_collection_state( + api: &GearApi, + program_id: &ProgramId, +) -> Option> { + let reply = api + .read_state(*program_id, StateQuery::AllCollections.encode()) + .await + .expect("Unexpected invalid reply."); + if let StateReply::AllCollections(state) = reply { + Some(state) + } else { + None + } +} + +pub async fn get_marketplace_state(api: &GearApi, program_id: &ProgramId) -> Option { + let reply = api + .read_state(*program_id, StateQuery::All.encode()) + .await + .expect("Unexpected invalid reply."); + if let StateReply::All(state) = reply { + Some(state) + } else { + None + } +} + +pub async fn init_marketplace(api: &GearApi) -> Result<(MessageId, ProgramId), ()> { + let royalty_to_marketplace = 200; + + let init_marketplace = NftMarketplaceInit { + gas_for_creation: 200_000_000_000, + gas_for_mint: 100_000_000_000, + gas_for_transfer_token: 5_000_000_000, + gas_for_close_auction: 10_000_000_000, + gas_for_delete_collection: 5_000_000_000, + gas_for_get_info: 5_000_000_000, + time_between_create_collections: 3_600_000, // 1 hour in milliseconds + fee_per_uploaded_file: 257_142_857_100, + royalty_to_marketplace_for_trade: royalty_to_marketplace, + royalty_to_marketplace_for_mint: royalty_to_marketplace, + minimum_transfer_value: 10_300_000_000_000, // because roylty to marketplace + ms_in_block: 3_000, + } + .encode(); + + let path = "target/wasm32-unknown-unknown/debug/nft_marketplace.opt.wasm"; + + let gas_info = api + .calculate_upload_gas( + None, + gclient::code_from_os(path).unwrap(), + init_marketplace.clone(), + 0, + true, + ) + .await + .expect("Error calculate upload gas"); + + let (message_id, program_id, _) = api + .upload_program_bytes( + gclient::code_from_os(path).unwrap(), + gclient::now_micros().to_le_bytes(), + init_marketplace, + gas_info.min_limit, + 0, + ) + .await + .expect("Error upload program bytes"); + Ok((message_id, program_id)) +} + +pub async fn add_new_collection( + api: &GearApi, + marketplace_program_id: ProgramId, +) -> Result { + let (nft_code_id, _) = api + .upload_code_by_path("target/wasm32-unknown-unknown/debug/nft.opt.wasm") + .await + .expect("Error upload code"); + + let nft_code_id: [u8; 32] = nft_code_id.into(); + + let add_collection_payload = NftMarketplaceAction::AddNewCollection { + code_id: nft_code_id.into(), + meta_link: String::from("My Meta"), + type_name: String::from("Simple NFT"), + type_description: String::from("My Collection"), + }; + + let gas_info = api + .calculate_handle_gas( + None, + marketplace_program_id, + add_collection_payload.encode(), + 0, + true, + ) + .await + .expect("Error calculate gas"); + + let (message_id, _) = api + .send_message( + marketplace_program_id, + add_collection_payload, + gas_info.min_limit, + 0, + ) + .await + .expect("Error send message"); + + Ok(message_id) +} + +pub async fn create_collection( + api: &GearApi, + marketplace_program_id: ProgramId, +) -> Result { + let img_data = ImageData { + limit_copies: Some(1), + auto_changing_rules: None, + }; + let img_links_and_data: Vec<(String, ImageData)> = (0..10) + .map(|i| (format!("Img-{}", i), img_data.clone())) + .collect(); + + let init_nft_payload = NftInit { + collection_owner: USERS[0].into(), + config: Config { + name: "User Collection".to_string(), + description: "User Collection".to_string(), + collection_banner: "Collection banner".to_string(), + collection_logo: "Collection logo".to_string(), + collection_tags: vec!["tag1".to_string()], + additional_links: None, + royalty: 0, + user_mint_limit: Some(3), + payment_for_mint: 10_000_000_000_000, + transferable: Some(0), + sellable: Some(0), + }, + img_links_and_data, + permission_to_mint: None, + fee_per_uploaded_file: 257_142_857_100, + } + .encode(); + + let create_collection_payload = NftMarketplaceAction::CreateCollection { + type_name: String::from("Simple NFT"), + payload: init_nft_payload, + }; + let gas_info = api + .calculate_handle_gas( + None, + marketplace_program_id, + create_collection_payload.encode(), + 10_000_000_000_000, + true, + ) + .await + .expect("Error calculate gas"); + + let (message_id, _) = api + .send_message( + marketplace_program_id, + create_collection_payload, + gas_info.min_limit, + 10_000_000_000_000, + ) + .await + .expect("Error send message"); + + Ok(message_id) +} + +pub async fn mint( + api: &GearApi, + marketplace_program_id: ProgramId, + address_nft: ActorId, + payment_for_mint: u128, +) -> Result { + let gas_info = api + .calculate_handle_gas( + None, + marketplace_program_id, + NftMarketplaceAction::Mint { + collection_address: address_nft, + } + .encode(), + payment_for_mint, + true, + ) + .await + .expect("Error calculate gas"); + + let (message_id, _) = api + .send_message( + marketplace_program_id, + NftMarketplaceAction::Mint { + collection_address: address_nft, + }, + gas_info.min_limit, + payment_for_mint, + ) + .await + .expect("Error send message"); + + Ok(message_id) +} + +pub async fn approve( + api: &GearApi, + marketplace_program_id: ProgramId, + nft_program_id: ProgramId, +) -> Result { + let address_marketplace: ActorId = marketplace_program_id.into_bytes().into(); + let gas_info = api + .calculate_handle_gas( + None, + nft_program_id, + NftAction::Approve { + to: address_marketplace, + token_id: 0, + } + .encode(), + 0, + true, + ) + .await + .expect("Error calculate gas"); + + let (message_id, _) = api + .send_message( + nft_program_id, + NftAction::Approve { + to: address_marketplace, + token_id: 0, + }, + gas_info.min_limit, + 0, + ) + .await + .expect("Error send message"); + + Ok(message_id) +} + +pub async fn get_new_client(api: &GearApi, name: &str) -> GearApi { + let alice_balance = api + .total_balance(api.account_id()) + .await + .expect("Error total balance"); + let amount = alice_balance / 10; + api.transfer( + api.get_specific_actor_id(name) + .encode() + .as_slice() + .try_into() + .expect("Unexpected invalid `ProgramId`."), + amount, + ) + .await + .expect("Error transfer"); + + api.clone() + .with(USERS_STR[0]) + .expect("Unable to change signer.") +} From 7dfd4bcc5ce051e0eb8891528fc1a7b196de2a75 Mon Sep 17 00:00:00 2001 From: MedovTimur Date: Wed, 6 Mar 2024 14:10:10 +0300 Subject: [PATCH 5/8] some changes --- Cargo.toml | 17 +-- io/src/lib.rs | 38 +++-- nft/io/src/lib.rs | 43 ++---- nft/src/lib.rs | 173 ++++++++------------- rust-toolchain.toml | 5 - src/auction.rs | 32 ++-- src/lib.rs | 102 ++++++++----- src/nft_messages.rs | 2 +- src/offer.rs | 8 +- src/payment.rs | 11 +- src/sale.rs | 10 +- tests/marketplace_node_test.rs | 270 ++++++++++++++++++++++++++------- tests/test_marketplace.rs | 14 +- tests/test_nft.rs | 152 +------------------ tests/utils/mod.rs | 28 ++-- tests/utils_gclient/mod.rs | 45 +++--- 16 files changed, 464 insertions(+), 486 deletions(-) delete mode 100644 rust-toolchain.toml diff --git a/Cargo.toml b/Cargo.toml index 362e507..f5a8875 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ license.workspace = true authors.workspace = true [dependencies] -gstd.workspace = true +gstd = { workspace = true, features = ["debug"] } hashbrown = "0.13" parity-scale-codec.workspace = true scale-info.workspace = true @@ -37,7 +37,6 @@ primitive-types.workspace = true parity-scale-codec.workspace = true scale-info.workspace = true hex = "0.4.3" -serial_test = "*" [build-dependencies] @@ -55,13 +54,13 @@ license = "MIT" authors = ["Gear Technologies"] [workspace.dependencies] -gstd = { git = "https://github.com/gear-tech/gear", tag = "v1.0.2" } -gear-wasm-builder = { git = "https://github.com/gear-tech/gear", tag = "v1.0.2" } -gmeta = { git = "https://github.com/gear-tech/gear", tag = "v1.0.2" } -gclient = { git = "https://github.com/gear-tech/gear", tag = "v1.0.2" } -gtest = { git = "https://github.com/gear-tech/gear"} -gear-core = { git = "https://github.com/gear-tech/gear", tag = "v1.0.2" } -gcore = { git = "https://github.com/gear-tech/gear", tag = "v1.0.2" } +gstd = "1.1.0" +gear-wasm-builder = "1.1.0" +gmeta = "1.1.0" +gclient = "1.1.0" +gtest = { git = "https://github.com/gear-tech/gear", tag = "v1.1.0" } +gear-core = "1.1.0" +gcore = "1.1.0" primitive-types = { version = "0.12", default-features = false } nft-marketplace-io = { path = "io" } nft-io = { path = "nft/io" } diff --git a/io/src/lib.rs b/io/src/lib.rs index dd0d48b..2f02096 100644 --- a/io/src/lib.rs +++ b/io/src/lib.rs @@ -1,9 +1,6 @@ #![no_std] use gmeta::{In, InOut, Metadata}; use gstd::{prelude::*, ActorId, CodeId}; - -pub const EXISTENTIAL_DEPOSIT: u128 = 10_000_000_000_000; - pub struct NftMarketplaceMetadata; impl Metadata for NftMarketplaceMetadata { type Init = In; @@ -25,7 +22,6 @@ impl Metadata for NftMarketplaceMetadata { /// * time_between_create_collections - time between collection creation /// (to avoid regular users from creating collections too often) /// * minimum_transfer_value - minimum allowable transfer value -/// * ms_in_block - number of milliseconds in one block /// (this variable is needed to correctly calculate the time for the delayed message in the auction) #[derive(Encode, Decode, TypeInfo)] pub struct NftMarketplaceInit { @@ -36,11 +32,12 @@ pub struct NftMarketplaceInit { pub gas_for_delete_collection: u64, pub gas_for_get_info: u64, pub time_between_create_collections: u64, - pub fee_per_uploaded_file: u128, pub royalty_to_marketplace_for_trade: u16, pub royalty_to_marketplace_for_mint: u16, - pub minimum_transfer_value: u128, pub ms_in_block: u32, + pub fee_per_uploaded_file: u128, + pub max_creator_royalty: u16, + pub max_number_of_images: u64, } #[derive(Encode, Decode, TypeInfo)] @@ -117,11 +114,12 @@ pub enum NftMarketplaceAction { gas_for_delete_collection: Option, gas_for_get_info: Option, time_between_create_collections: Option, - fee_per_uploaded_file: Option, royalty_to_marketplace_for_trade: Option, royalty_to_marketplace_for_mint: Option, - minimum_transfer_value: Option, ms_in_block: Option, + fee_per_uploaded_file: Option, + max_creator_royalty: Option, + max_number_of_images: Option, }, } @@ -129,10 +127,12 @@ pub enum NftMarketplaceAction { pub enum NftMarketplaceEvent { Initialized { time_between_create_collections: u64, - fee_per_uploaded_file: u128, royalty_to_marketplace_for_trade: u16, royalty_to_marketplace_for_mint: u16, - minimum_transfer_value: u128, + minimum_value_for_trade: u128, + fee_per_uploaded_file: u128, + max_creator_royalty: u16, + max_number_of_images: u64, }, NewCollectionAdded { code_id: CodeId, @@ -168,7 +168,7 @@ pub enum NftMarketplaceEvent { collection_address: ActorId, token_id: u64, min_price: u128, - duration: u32, + duration_ms: u32, }, AuctionClosed { collection_address: ActorId, @@ -214,15 +214,18 @@ pub enum NftMarketplaceEvent { gas_for_delete_collection: Option, gas_for_get_info: Option, time_between_create_collections: Option, - fee_per_uploaded_file: Option, royalty_to_marketplace_for_trade: Option, royalty_to_marketplace_for_mint: Option, - minimum_transfer_value: Option, ms_in_block: Option, + minimum_value_for_trade: Option, + fee_per_uploaded_file: Option, + max_creator_royalty: Option, + max_number_of_images: Option, }, BalanceHasBeenWithdrawn { value: u128, }, + ValueSent, } #[derive(Debug, Clone, Encode, Decode, TypeInfo)] @@ -231,7 +234,6 @@ pub enum NftMarketplaceError { AlreadyOnAuction, AuctionClosed, AlreadyOnSale, - LessThanExistentialDeposit, WrongReply, WrongCollectionName, AccessDenied, @@ -249,6 +251,8 @@ pub enum NftMarketplaceError { ErrorGetInfo, WrongValue, MintError, + InsufficientBalance, + LessThanMinimumValueForTrade } #[derive(Encode, Decode, TypeInfo)] @@ -281,6 +285,7 @@ pub struct State { pub auctions: Vec<((ActorId, u64), Auction)>, pub offers: Vec<(Offer, u128)>, pub config: Config, + pub minimum_value_for_trade: u128, } /// * code_id - code_id is used to create a collection by that CodeId, @@ -310,11 +315,12 @@ pub struct Config { pub gas_for_delete_collection: u64, pub gas_for_get_info: u64, pub time_between_create_collections: u64, - pub fee_per_uploaded_file: u128, pub royalty_to_marketplace_for_trade: u16, pub royalty_to_marketplace_for_mint: u16, - pub minimum_transfer_value: u128, pub ms_in_block: u32, + pub fee_per_uploaded_file: u128, + pub max_creator_royalty: u16, + pub max_number_of_images: u64, } #[derive(Debug, Encode, Decode, TypeInfo, Clone)] diff --git a/nft/io/src/lib.rs b/nft/io/src/lib.rs index 44a31a6..ec7d131 100644 --- a/nft/io/src/lib.rs +++ b/nft/io/src/lib.rs @@ -5,9 +5,8 @@ use gstd::{prelude::*, ActorId}; pub type NftId = u64; pub type TimeSec = u32; -pub const BLOCK_DURATION_IN_SECS: u32 = 3; -pub const EXISTENTIAL_DEPOSIT: u128 = 10_000_000_000_000; -pub const GAS_AUTO_CHANGING: u64 = 5_000_000_000; +pub const FEE_PER_UPLOADED_FILE: u128 = 282_857_142_900; +pub const MAX_CREATOR_ROYALTY: u16 = 1_000; // 10% pub struct ContractMetadata; @@ -26,19 +25,11 @@ pub struct NftInit { pub config: Config, pub img_links_and_data: Vec<(String, ImageData)>, pub permission_to_mint: Option>, - pub fee_per_uploaded_file: u128, } #[derive(Debug, Clone, Encode, Decode, TypeInfo)] pub struct ImageData { pub limit_copies: Option, - pub auto_changing_rules: Option>, -} - -#[derive(Debug, Clone, Encode, Decode, TypeInfo)] -pub enum Action { - ChangeImg(String), - AddMeta(String), } #[derive(Debug, Clone, Encode, Decode, TypeInfo)] @@ -97,14 +88,6 @@ pub enum NftAction { ChangeConfig { config: Config, }, - ChangeImg { - token_id: NftId, - img_link: String, - }, - AddMetadata { - token_id: NftId, - metadata: String, - }, AddUsersForMint { users: Vec, }, @@ -112,6 +95,12 @@ pub enum NftAction { user: ActorId, }, LiftRestrictionMint, + AddAdmin { + admin: ActorId, + }, + RemoveAdmin { + admin: ActorId, + }, } #[derive(Debug, Clone, Encode, Decode, TypeInfo)] @@ -156,14 +145,6 @@ pub enum NftEvent { ConfigChanged { config: Config, }, - ImageChanged { - token_id: NftId, - img_link: String, - }, - MetadataAdded { - token_id: NftId, - metadata: String, - }, UsersForMintAdded { users: Vec, }, @@ -171,6 +152,12 @@ pub enum NftEvent { user: ActorId, }, LiftRestrictionMint, + AdminAdded { + admin: ActorId, + }, + AdminRemoved { + admin: ActorId, + } } #[derive(Debug, Clone, Encode, Decode, TypeInfo)] pub enum NftError { @@ -191,6 +178,7 @@ pub enum NftError { ThereIsNoSuchUser, ExhaustedLimit, WrongValue, + OnlyOneAdminLeft } #[derive(Debug, Clone, Encode, Decode, TypeInfo)] @@ -204,6 +192,7 @@ pub struct NftState { pub collection_owner: ActorId, pub total_number_of_tokens: Option, pub permission_to_mint: Option>, + pub marketplace_address: ActorId, } #[derive(Debug, Clone, Encode, Decode, TypeInfo)] diff --git a/nft/src/lib.rs b/nft/src/lib.rs index 89e6cd4..7fd3d0b 100644 --- a/nft/src/lib.rs +++ b/nft/src/lib.rs @@ -5,10 +5,7 @@ use gstd::{ prelude::*, ActorId, }; -use nft_io::{ - Action, Config, ImageData, Nft, NftAction, NftError, NftEvent, NftId, NftInit, NftState, - StateQuery, StateReply, BLOCK_DURATION_IN_SECS, EXISTENTIAL_DEPOSIT, GAS_AUTO_CHANGING, -}; +use nft_io::*; #[derive(Debug)] struct NftContract { @@ -22,6 +19,8 @@ struct NftContract { pub collection_owner: ActorId, pub permission_to_mint: Option>, pub total_number_of_tokens: Option, + pub marketplace_address: ActorId, + pub admins: HashSet, } static mut NFT_CONTRACT: Option = None; @@ -49,35 +48,12 @@ impl NftContract { .get_mut(rand_index as usize) .ok_or(NftError::ErrorGettingRandom)?; - img_info.limit_copies.as_mut().map(|limit| *limit -= 1); + if let Some(limit) = img_info.limit_copies.as_mut() { + *limit -= 1; + } let img_link = link.clone(); - if let Some(auto_changing_rules) = &img_info.auto_changing_rules { - auto_changing_rules - .iter() - .for_each(|(trigger_time, auto_action)| { - let action = match &auto_action { - Action::ChangeImg(img_link) => NftAction::ChangeImg { - token_id, - img_link: img_link.to_string(), - }, - Action::AddMeta(metadata) => NftAction::AddMetadata { - token_id, - metadata: metadata.to_string(), - }, - }; - msg::send_with_gas_delayed( - exec::program_id(), - action, - GAS_AUTO_CHANGING, - 0, - *trigger_time / BLOCK_DURATION_IN_SECS, - ) - .expect("Error in sending a delayed message `NftAction`"); - }); - } - // if there are 0 copies of this token left, then delete this token if let Some(0) = img_info.limit_copies { self.img_links_and_data.remove(rand_index as usize); @@ -210,7 +186,7 @@ impl NftContract { } fn expand(&mut self, additional_links: Vec<(String, ImageData)>) -> Result { let msg_src = msg::source(); - self.check_collection_owner(msg_src)?; + self.check_admin(msg_src)?; if additional_links .iter() .any(|(_, img_data)| img_data.limit_copies.map_or(false, |limit| limit == 0)) @@ -221,9 +197,9 @@ impl NftContract { self.img_links_and_data.extend(additional_links.clone()); let mut number_tokens = sum_limit_copies(&self.img_links_and_data); // The tokens that have already been minted should be added as well - number_tokens - .as_mut() - .map(|all_copies| *all_copies += self.tokens.len() as u64); + if let Some(all_copies) = number_tokens.as_mut() { + *all_copies += self.tokens.len() as u64; + } self.total_number_of_tokens = number_tokens; Ok(NftEvent::Expanded { @@ -234,7 +210,7 @@ impl NftContract { fn change_config(&mut self, config: Config) -> Result { let msg_src = msg::source(); - self.check_collection_owner(msg_src)?; + self.check_admin(msg_src)?; if !self.tokens.is_empty() { return Err(NftError::ConfigCannotBeChanged); @@ -257,73 +233,27 @@ impl NftContract { Ok(NftEvent::ConfigChanged { config }) } - fn change_image(&mut self, token_id: NftId, img_link: String) -> Result { - if exec::program_id() != msg::source() { - return Err(NftError::AccessDenied); - } - - let nft = self - .tokens - .get_mut(&token_id) - .ok_or(NftError::TokenDoesNotExist)?; - nft.media_url = img_link.clone(); - - msg::send( - nft.owner, - NftEvent::ImageChanged { - token_id, - img_link: img_link.clone(), - }, - 0, - ) - .expect("Error in sending NftEvent::ImageChanged"); - Ok(NftEvent::ImageChanged { token_id, img_link }) - } - - fn add_metadata(&mut self, token_id: NftId, metadata: String) -> Result { - if exec::program_id() != msg::source() { - return Err(NftError::AccessDenied); - } - let nft = self - .tokens - .get_mut(&token_id) - .ok_or(NftError::TokenDoesNotExist)?; - nft.metadata.push(metadata.clone()); - - msg::send( - nft.owner, - NftEvent::MetadataAdded { - token_id, - metadata: metadata.clone(), - }, - 0, - ) - .expect("Error in sending NftEvent::MetadataAdded"); - Ok(NftEvent::MetadataAdded { token_id, metadata }) - } - fn add_users_for_mint(&mut self, users: Vec) -> Result { let msg_src = msg::source(); - self.check_collection_owner(msg_src)?; + self.check_admin(msg_src)?; if let Some(allowed_users) = &mut self.permission_to_mint { allowed_users.extend(users.clone()); allowed_users.sort(); allowed_users.dedup(); + } else if self.tokens.is_empty() { + let mut new_users = users.clone(); + new_users.push(msg_src); + self.permission_to_mint = Some(new_users); } else { - if self.tokens.is_empty() { - let mut new_users = users.clone(); - new_users.push(msg_src); - self.permission_to_mint = Some(new_users); - } else { - return Err(NftError::UserRestrictionCannotBeChanged); - } + return Err(NftError::UserRestrictionCannotBeChanged); } + Ok(NftEvent::UsersForMintAdded { users }) } fn delete_user_for_mint(&mut self, user: ActorId) -> Result { let msg_src = msg::source(); - self.check_collection_owner(msg_src)?; + self.check_admin(msg_src)?; let allowed_users = self .permission_to_mint .as_mut() @@ -342,7 +272,7 @@ impl NftContract { fn lift_restrictions_mint(&mut self) -> Result { let msg_src = msg::source(); - self.check_collection_owner(msg_src)?; + self.check_admin(msg_src)?; if !self.tokens.is_empty() { return Err(NftError::UserRestrictionCannotBeChanged); @@ -351,13 +281,29 @@ impl NftContract { Ok(NftEvent::LiftRestrictionMint) } + + fn add_admin(&mut self, admin: ActorId) -> Result { + let msg_src = msg::source(); + self.check_admin(msg_src)?; + self.admins.insert(admin); + Ok(NftEvent::AdminAdded { admin}) + } + fn remove_admin(&mut self, admin: ActorId) -> Result { + let msg_src = msg::source(); + self.check_admin(msg_src)?; + if self.admins.len() == 1 { + return Err(NftError::OnlyOneAdminLeft); + } + self.admins.remove(&admin); + Ok(NftEvent::AdminRemoved { admin}) + } fn can_delete(&self) -> Result { Ok(NftEvent::CanDelete(self.tokens.is_empty())) } - fn check_collection_owner(&self, msg_src: ActorId) -> Result<(), NftError> { - if self.collection_owner != msg_src { + fn check_admin(&self, msg_src: ActorId) -> Result<(), NftError> { + if !self.admins.contains(&msg_src) { return Err(NftError::AccessDenied); } Ok(()) @@ -463,14 +409,15 @@ extern "C" fn init() { config, img_links_and_data, mut permission_to_mint, - fee_per_uploaded_file, } = msg::load().expect("Unable to decode `NftInit`."); let msg_value = msg::value(); - let picture_fee = fee_per_uploaded_file * img_links_and_data.len() as u128; + let picture_fee = FEE_PER_UPLOADED_FILE * img_links_and_data.len() as u128; - if picture_fee < EXISTENTIAL_DEPOSIT && msg_value != EXISTENTIAL_DEPOSIT - || picture_fee >= EXISTENTIAL_DEPOSIT && msg_value != picture_fee + let existential_deposit = exec::env_vars().existential_deposit; + + if picture_fee < existential_deposit && msg_value != existential_deposit + || picture_fee >= existential_deposit && msg_value != picture_fee { panic!("Wrong value for picture fee"); } @@ -483,20 +430,21 @@ extern "C" fn init() { "The mint limit must be greater than zero" ); - if config.payment_for_mint > 0 && config.payment_for_mint < EXISTENTIAL_DEPOSIT { + if config.payment_for_mint > 0 && config.payment_for_mint < existential_deposit { panic!( "{}", format!( "The payment for mint must be greater than existential deposit ({})", - EXISTENTIAL_DEPOSIT + existential_deposit ) ); } - // made 10_000 so hundredths of a percent could be entered. - if config.royalty > 10_000 { - panic!("Royalty percent must be less than 100%"); + // can't take more than MAX_CREATOR_ROYALTY + if config.royalty > MAX_CREATOR_ROYALTY { + panic!("Royalty percent must be less"); } + // can't be made sellable but not transferable. if config.transferable.is_none() && config.sellable.is_some() { panic!("Tokens must be transferable"); @@ -521,6 +469,8 @@ extern "C" fn init() { } } + let mut admins = HashSet::new(); + admins.insert(collection_owner); unsafe { NFT_CONTRACT = Some(NftContract { tokens: HashMap::new(), @@ -533,6 +483,8 @@ extern "C" fn init() { collection_owner, total_number_of_tokens, permission_to_mint: permission_to_mint.clone(), + marketplace_address: msg::source(), + admins, }) }; msg::send( @@ -570,7 +522,12 @@ extern "C" fn handle() { NftAction::Mint { minter } => { let msg_source = msg::source(); let msg_value = msg::value(); - let result = nft_contract.mint(minter, msg_value); + let result = if msg_source != nft_contract.marketplace_address { + Err(NftError::AccessDenied) + } else { + nft_contract.mint(minter, msg_value) + }; + if result.is_err() { msg::send_with_gas(msg_source, "", 0, msg_value).expect("Error in sending value"); } @@ -589,15 +546,11 @@ extern "C" fn handle() { NftAction::GetTokenInfo { token_id } => nft_contract.get_token_info(token_id), NftAction::GetPaymentForMint => nft_contract.get_payment_for_mint(), NftAction::CanDelete => nft_contract.can_delete(), - NftAction::ChangeImg { token_id, img_link } => { - nft_contract.change_image(token_id, img_link) - } - NftAction::AddMetadata { token_id, metadata } => { - nft_contract.add_metadata(token_id, metadata) - } NftAction::AddUsersForMint { users } => nft_contract.add_users_for_mint(users), NftAction::DeleteUserForMint { user } => nft_contract.delete_user_for_mint(user), NftAction::LiftRestrictionMint => nft_contract.lift_restrictions_mint(), + NftAction::AddAdmin { admin } => nft_contract.add_admin(admin), + NftAction::RemoveAdmin { admin } => nft_contract.remove_admin(admin), }; msg::reply(result, 0).expect("Failed to encode or reply with `Result`."); @@ -635,12 +588,12 @@ impl From for NftState { collection_owner, total_number_of_tokens, permission_to_mint, + marketplace_address, .. } = value; let tokens = tokens .into_iter() - .map(|(nft_id, nft)| (nft_id, nft)) .collect(); let owners = owners .into_iter() @@ -648,7 +601,6 @@ impl From for NftState { .collect(); let token_approvals = token_approvals .into_iter() - .map(|(nft_id, actor_id)| (nft_id, actor_id)) .collect(); Self { @@ -661,6 +613,7 @@ impl From for NftState { collection_owner, total_number_of_tokens, permission_to_mint, + marketplace_address } } } diff --git a/rust-toolchain.toml b/rust-toolchain.toml deleted file mode 100644 index 07264bc..0000000 --- a/rust-toolchain.toml +++ /dev/null @@ -1,5 +0,0 @@ -[toolchain] -channel = "nightly-2023-09-18" -targets = ["wasm32-unknown-unknown"] -profile = "minimal" -components = ["rustfmt", "clippy"] diff --git a/src/auction.rs b/src/auction.rs index 1df90d3..b4b7d6c 100644 --- a/src/auction.rs +++ b/src/auction.rs @@ -22,8 +22,8 @@ impl NftMarketplace { return Err(NftMarketplaceError::AlreadyOnSale); } - if min_price < self.config.minimum_transfer_value { - return Err(NftMarketplaceError::LessThanExistentialDeposit); + if min_price < self.minimum_value_for_trade { + return Err(NftMarketplaceError::LessThanMinimumValueForTrade); } // check token info @@ -51,12 +51,13 @@ impl NftMarketplace { ) .await? { + let current_time = exec::block_timestamp(); self.auctions .entry((collection_address, token_id)) .or_insert(Auction { owner: msg_src, - started_at: exec::block_timestamp(), - ended_at: exec::block_timestamp() + (duration * self.config.ms_in_block) as u64, + started_at: current_time, + ended_at: current_time + (duration * self.config.ms_in_block) as u64, current_price: min_price, current_winner: ActorId::zero(), collection_owner, @@ -74,7 +75,7 @@ impl NftMarketplace { }, self.config.gas_for_close_auction, 0, - duration, + duration+1, ) .expect("Error in sending delayed message"); @@ -82,7 +83,7 @@ impl NftMarketplace { collection_address, token_id, min_price, - duration, + duration_ms: (duration+1) * self.config.ms_in_block, }) } @@ -97,7 +98,7 @@ impl NftMarketplace { if auction.current_winner != ActorId::zero() { // use send_with_gas with gas_limit = 0 to transfer the value directly to the balance, not to the mailbox. - msg::send_with_gas(auction.current_winner, "", 0, auction.current_price) + msg::send_with_gas(auction.current_winner, NftMarketplaceEvent::ValueSent, 0, auction.current_price) .expect("Error in sending value"); } auction.current_winner = msg_src; @@ -119,7 +120,6 @@ impl NftMarketplace { if msg_src != exec::program_id() && !self.admins.contains(&msg_src) { return Err(NftMarketplaceError::AccessDenied); } - let auction = self .auctions .get(&(collection_address, token_id)) @@ -144,7 +144,6 @@ impl NftMarketplace { self.config.gas_for_transfer_token, ) .await?; - // transfer value to buyer and percent to collection creator currency_transfer( auction.collection_owner, @@ -152,7 +151,6 @@ impl NftMarketplace { auction.current_price, auction.royalty, self.config.royalty_to_marketplace_for_trade, - self.config.minimum_transfer_value, ); } let price = auction.current_price; @@ -162,7 +160,6 @@ impl NftMarketplace { self.auctions .remove(&(collection_address, token_id)) .expect("Can't be None"); - msg::send( auction_creator, Ok::(NftMarketplaceEvent::AuctionClosed { @@ -174,7 +171,6 @@ impl NftMarketplace { 0, ) .expect("Error during send to owner `NftMarketplaceEvent::AuctionClosed`"); - Ok(NftMarketplaceEvent::AuctionClosed { collection_address, token_id, @@ -206,7 +202,7 @@ impl NftMarketplace { if auction.current_winner != ActorId::zero() { // use send_with_gas to transfer the value directly to the balance, not to the mailbox. - msg::send_with_gas(auction.current_winner, "", 0, auction.current_price) + msg::send_with_gas(auction.current_winner, NftMarketplaceEvent::ValueSent, 0, auction.current_price) .expect("Error in sending value"); } @@ -230,16 +226,18 @@ impl NftMarketplace { let auction = if let Some(auction) = self.auctions.get_mut(&(*collection_address, *token_id)) { if auction.ended_at < exec::block_timestamp() { - msg::send_with_gas(*msg_src, "", 0, *bid).expect("Error in sending value"); + msg::send_with_gas(*msg_src, NftMarketplaceEvent::ValueSent, 0, *bid).expect("Error in sending value"); return Err(NftMarketplaceError::AuctionClosed); } - if *bid <= auction.current_price { - msg::send_with_gas(*msg_src, "", 0, *bid).expect("Error in sending value"); + + // if the first bid, it may be equal to the `current_price` (initial bid) + if auction.current_winner != ActorId::zero() && *bid <= auction.current_price || auction.current_winner == ActorId::zero() && *bid < auction.current_price { + msg::send_with_gas(*msg_src, NftMarketplaceEvent::ValueSent, 0, *bid).expect("Error in sending value"); return Err(NftMarketplaceError::LessOrEqualThanBid); } auction } else { - msg::send_with_gas(*msg_src, "", 0, *bid).expect("Error in sending value"); + msg::send_with_gas(*msg_src, NftMarketplaceEvent::ValueSent, 0, *bid).expect("Error in sending value"); return Err(NftMarketplaceError::ThereIsNoSuchAuction); }; Ok(auction) diff --git a/src/lib.rs b/src/lib.rs index 08b5419..f3060df 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ use crate::nft_messages::*; use gstd::{ - collections::HashMap, debug, exec, msg, prelude::*, prog::ProgramGenerator, ActorId, CodeId, + collections::HashMap, exec, msg, prelude::*, prog::ProgramGenerator, ActorId, CodeId }; use nft_marketplace_io::*; @@ -27,6 +27,7 @@ pub struct NftMarketplace { pub auctions: HashMap<(CollectionId, TokenId), Auction>, pub offers: HashMap, pub config: Config, + pub minimum_value_for_trade: u128, } static mut NFT_MARKETPLACE: Option = None; @@ -41,13 +42,15 @@ extern "C" fn init() { gas_for_delete_collection, gas_for_get_info, time_between_create_collections, - fee_per_uploaded_file, royalty_to_marketplace_for_trade, royalty_to_marketplace_for_mint, - minimum_transfer_value, ms_in_block, + fee_per_uploaded_file, + max_creator_royalty, + max_number_of_images, } = msg::load().expect("Unable to decode `NftMarketplaceInit`"); - + let existential_deposit = exec::env_vars().existential_deposit; + let minimum_value_for_trade = existential_deposit * 10_000 / (10_000 - royalty_to_marketplace_for_trade) as u128; let nft_marketplace = NftMarketplace { admins: vec![msg::source()], config: Config { @@ -58,22 +61,26 @@ extern "C" fn init() { gas_for_delete_collection, gas_for_get_info, time_between_create_collections, - fee_per_uploaded_file, royalty_to_marketplace_for_trade, royalty_to_marketplace_for_mint, - minimum_transfer_value, ms_in_block, + fee_per_uploaded_file, + max_creator_royalty, + max_number_of_images }, + minimum_value_for_trade, ..Default::default() }; unsafe { NFT_MARKETPLACE = Some(nft_marketplace) }; msg::reply( Ok::(NftMarketplaceEvent::Initialized { time_between_create_collections, - fee_per_uploaded_file, royalty_to_marketplace_for_trade, royalty_to_marketplace_for_mint, - minimum_transfer_value, + minimum_value_for_trade, + fee_per_uploaded_file, + max_creator_royalty, + max_number_of_images }), 0, ) @@ -129,7 +136,7 @@ async fn main() { .buy(collection_address, token_id, msg_source, msg_value) .await; if reply.is_err() { - msg::send_with_gas(msg_source, "", 0, msg_value).expect("Error in sending value"); + msg::send_with_gas(msg_source, NftMarketplaceEvent::ValueSent, 0, msg_value).expect("Error in sending value"); } reply } @@ -174,7 +181,7 @@ async fn main() { .create_offer(collection_address, token_id, msg_source, msg_value) .await; if reply.is_err() { - msg::send_with_gas(msg_source, "", 0, msg_value).expect("Error in sending value"); + msg::send_with_gas(msg_source, NftMarketplaceEvent::ValueSent, 0, msg_value).expect("Error in sending value"); } reply } @@ -198,9 +205,10 @@ async fn main() { time_between_create_collections, royalty_to_marketplace_for_trade, royalty_to_marketplace_for_mint, - fee_per_uploaded_file, - minimum_transfer_value, ms_in_block, + fee_per_uploaded_file, + max_creator_royalty, + max_number_of_images } => nft_marketplace.update_config( gas_for_creation, gas_for_mint, @@ -209,11 +217,12 @@ async fn main() { gas_for_delete_collection, gas_for_get_info, time_between_create_collections, - fee_per_uploaded_file, royalty_to_marketplace_for_trade, royalty_to_marketplace_for_mint, - minimum_transfer_value, ms_in_block, + fee_per_uploaded_file, + max_creator_royalty, + max_number_of_images ), }; @@ -270,12 +279,12 @@ impl NftMarketplace { Ok(future) => match future.await { Ok((address, _)) => address, Err(_) => { - msg::send_with_gas(msg_src, "", 0, msg_value).expect("Error in sending value"); + msg::send_with_gas(msg_src, NftMarketplaceEvent::ValueSent, 0, msg_value).expect("Error in sending value"); return Err(NftMarketplaceError::CreationError); } }, Err(_) => { - msg::send_with_gas(msg_src, "", 0, msg_value).expect("Error in sending value"); + msg::send_with_gas(msg_src, NftMarketplaceEvent::ValueSent, 0, msg_value).expect("Error in sending value"); return Err(NftMarketplaceError::CreationError); } }; @@ -309,7 +318,7 @@ impl NftMarketplace { ) .await; if reply.is_err() { - msg::send_with_gas(msg_src, "", 0, msg_value).expect("Error in sending value"); + msg::send_with_gas(msg_src, NftMarketplaceEvent::ValueSent, 0, msg_value).expect("Error in sending value"); } reply } @@ -382,11 +391,12 @@ impl NftMarketplace { gas_for_delete_collection: Option, gas_for_get_info: Option, time_between_create_collections: Option, - fee_per_uploaded_file: Option, royalty_to_marketplace_for_trade: Option, royalty_to_marketplace_for_mint: Option, - minimum_transfer_value: Option, ms_in_block: Option, + fee_per_uploaded_file: Option, + max_creator_royalty: Option, + max_number_of_images: Option, ) -> Result { let msg_src = msg::source(); self.check_admin(msg_src)?; @@ -411,20 +421,28 @@ impl NftMarketplace { if let Some(time) = time_between_create_collections { self.config.time_between_create_collections = time; } - if let Some(fee) = fee_per_uploaded_file { - self.config.fee_per_uploaded_file = fee; - } - if let Some(royalty) = royalty_to_marketplace_for_trade { + let minimum_value_for_trade = if let Some(royalty) = royalty_to_marketplace_for_trade { self.config.royalty_to_marketplace_for_trade = royalty; - } + let existential_deposit = exec::env_vars().existential_deposit; + self.minimum_value_for_trade = existential_deposit * 10_000 / (10_000 - royalty) as u128; + Some(self.minimum_value_for_trade) + } else { + None + }; if let Some(royalty) = royalty_to_marketplace_for_mint { self.config.royalty_to_marketplace_for_mint = royalty; } - if let Some(min_value) = minimum_transfer_value { - self.config.minimum_transfer_value = min_value; + if let Some(ms_in_block) = ms_in_block { + self.config.ms_in_block = ms_in_block; } - if let Some(time_block) = ms_in_block { - self.config.ms_in_block = time_block; + if let Some(fee) = fee_per_uploaded_file { + self.config.fee_per_uploaded_file = fee; + } + if let Some(royalty) = max_creator_royalty { + self.config.max_creator_royalty = royalty; + } + if let Some(max_number_of_images) = max_number_of_images { + self.config.max_number_of_images = max_number_of_images; } Ok(NftMarketplaceEvent::ConfigUpdated { @@ -435,21 +453,26 @@ impl NftMarketplace { gas_for_delete_collection, gas_for_get_info, time_between_create_collections, - fee_per_uploaded_file, royalty_to_marketplace_for_trade, royalty_to_marketplace_for_mint, - minimum_transfer_value, ms_in_block, + minimum_value_for_trade, + fee_per_uploaded_file, + max_creator_royalty, + max_number_of_images }) } - pub fn balance_out(&mut self, value: u128) -> Result { + pub fn balance_out(&mut self) -> Result { let msg_src = msg::source(); self.check_admin(msg_src)?; - if value < EXISTENTIAL_DEPOSIT { - return Err(NftMarketplaceError::LessThanExistentialDeposit); + let balance = exec::value_available(); + let existential_deposit = exec::env_vars().existential_deposit; + if balance < 2*existential_deposit { + return Err(NftMarketplaceError::InsufficientBalance); } - msg::send_with_gas(msg_src, "", 0, value).expect("Error in sending value"); + let value = balance - existential_deposit; + msg::send_with_gas(msg_src, NftMarketplaceEvent::ValueSent, 0, value).expect("Error in sending value"); Ok(NftMarketplaceEvent::BalanceHasBeenWithdrawn { value }) } @@ -506,7 +529,6 @@ extern "C" fn state() { let type_collections = nft_marketplace .type_collections .into_iter() - .map(|(id, collection_info)| (id, collection_info)) .collect(); StateReply::CollectionsInfo(type_collections) } @@ -515,7 +537,6 @@ extern "C" fn state() { let collection_to_owner = nft_marketplace .collection_to_owner .into_iter() - .map(|(owner, collection_info)| (owner, collection_info)) .collect(); StateReply::AllCollections(collection_to_owner) } @@ -552,32 +573,28 @@ impl From for State { auctions, offers, config, + minimum_value_for_trade, } = value; let collection_to_owner = collection_to_owner .into_iter() - .map(|(owner, collection_info)| (owner, collection_info)) .collect(); let time_creation = time_creation .into_iter() - .map(|(id, time)| (id, time)) .collect(); let type_collections = type_collections .into_iter() - .map(|(id, collection_info)| (id, collection_info)) .collect(); - let sales = sales.into_iter().map(|(id, info)| (id, info)).collect(); + let sales = sales.into_iter().collect(); let auctions = auctions .into_iter() - .map(|(id, auction)| (id, auction)) .collect(); let offers = offers .into_iter() - .map(|(offer, price)| (offer, price)) .collect(); Self { @@ -589,6 +606,7 @@ impl From for State { auctions, offers, config, + minimum_value_for_trade, } } } diff --git a/src/nft_messages.rs b/src/nft_messages.rs index 2e7ad53..db312f1 100644 --- a/src/nft_messages.rs +++ b/src/nft_messages.rs @@ -1,4 +1,4 @@ -use gstd::{msg, prelude::*, ActorId}; +use gstd::{msg, prelude::*, ActorId, exec}; use nft_marketplace_io::*; pub async fn mint( diff --git a/src/offer.rs b/src/offer.rs index 68a36eb..3e84bf3 100644 --- a/src/offer.rs +++ b/src/offer.rs @@ -12,6 +12,9 @@ impl NftMarketplace { msg_src: ActorId, msg_value: u128, ) -> Result { + if msg_value < self.minimum_value_for_trade { + return Err(NftMarketplaceError::LessThanMinimumValueForTrade); + } if !self.collection_to_owner.contains_key(&collection_address) { return Err(NftMarketplaceError::WrongCollectionAddress); } @@ -56,7 +59,7 @@ impl NftMarketplace { self.offers .entry(offer.clone()) .and_modify(|price| { - msg::send_with_gas(offer.creator, "", 0, *price).expect("Error in sending value"); + msg::send_with_gas(offer.creator, NftMarketplaceEvent::ValueSent, 0, *price).expect("Error in sending value"); *price = msg_value; }) .or_insert(msg_value); @@ -81,7 +84,7 @@ impl NftMarketplace { if let Some((offer, price)) = self.offers.get_key_value(&offer) { // use send_with_gas to transfer the value directly to the balance, not to the mailbox. - msg::send_with_gas(offer.creator, "", 0, *price).expect("Error in sending value"); + msg::send_with_gas(offer.creator, NftMarketplaceEvent::ValueSent, 0, *price).expect("Error in sending value"); } else { return Err(NftMarketplaceError::WrongDataOffer); } @@ -143,7 +146,6 @@ impl NftMarketplace { *price, royalty, self.config.royalty_to_marketplace_for_trade, - self.config.minimum_transfer_value, ); self.offers.remove(&offer); diff --git a/src/payment.rs b/src/payment.rs index 2428e79..0f6de4c 100644 --- a/src/payment.rs +++ b/src/payment.rs @@ -1,4 +1,6 @@ +use gcore::exec; use gstd::{msg, ActorId}; +use nft_marketplace_io::NftMarketplaceEvent; pub fn currency_transfer( collection_owner: ActorId, @@ -6,25 +8,24 @@ pub fn currency_transfer( price: u128, royalty: u16, royalty_to_marketplace: u16, - minimum_transfer_value: u128, ) { // calculate the percentage to the creator of the collection // current_price * royalty / 10_000 let percent_to_collection_creator = price * (royalty as u128) / 10_000u128; let percent_to_marketplace = price * (royalty_to_marketplace as u128) / 10_000u128; - if percent_to_collection_creator > minimum_transfer_value { + if percent_to_collection_creator > exec::env_vars().existential_deposit { // use send_with_gas to transfer the value directly to the balance, not to the mailbox. - msg::send_with_gas(collection_owner, "", 0, percent_to_collection_creator) + msg::send_with_gas(collection_owner, NftMarketplaceEvent::ValueSent, 0, percent_to_collection_creator) .expect("Error in sending value"); msg::send_with_gas( token_owner, - "", + NftMarketplaceEvent::ValueSent, 0, price - percent_to_collection_creator - percent_to_marketplace, ) .expect("Error in sending value"); } else { - msg::send_with_gas(token_owner, "", 0, price - percent_to_marketplace) + msg::send_with_gas(token_owner, NftMarketplaceEvent::ValueSent, 0, price - percent_to_marketplace) .expect("Error in sending value"); } } diff --git a/src/sale.rs b/src/sale.rs index 4120b00..ce4878c 100644 --- a/src/sale.rs +++ b/src/sale.rs @@ -23,9 +23,9 @@ impl NftMarketplace { if self.auctions.contains_key(&(collection_address, token_id)) { return Err(NftMarketplaceError::AlreadyOnAuction); } - // check that the price is more than the minimum transfer value - if price < self.config.minimum_transfer_value { - return Err(NftMarketplaceError::LessThanExistentialDeposit); + // check that the price is more than the minimum value for trade + if price < self.minimum_value_for_trade { + return Err(NftMarketplaceError::LessThanMinimumValueForTrade); } // send a message to the nft contract to find out information about the token @@ -123,7 +123,6 @@ impl NftMarketplace { msg_value: u128, ) -> Result { self.check_sale(&collection_address, &token_id, msg_value)?; - // transfer the token to the buyer transfer_token( collection_address, @@ -132,13 +131,11 @@ impl NftMarketplace { self.config.gas_for_transfer_token, ) .await?; - let nft = self .sales .get(&(collection_address, token_id)) .expect("Can't be None") .clone(); - // transfer value to owner of token and percent to collection creator currency_transfer( nft.collection_owner, @@ -146,7 +143,6 @@ impl NftMarketplace { nft.price, nft.royalty, self.config.royalty_to_marketplace_for_trade, - self.config.minimum_transfer_value, ); // remove the sale from the marketplace self.sales diff --git a/tests/marketplace_node_test.rs b/tests/marketplace_node_test.rs index a91fbb1..f1b9a21 100644 --- a/tests/marketplace_node_test.rs +++ b/tests/marketplace_node_test.rs @@ -1,16 +1,16 @@ use gclient::{EventProcessor, GearApi, Result}; use gear_core::ids::ProgramId; -use gstd::prelude::*; +use gstd::{prelude::*, ActorId}; use nft_marketplace_io::*; -use std::time::Instant; +use nft_io::{Config as NftConfig, ImageData, NftInit}; mod utils_gclient; -use serial_test::serial; use utils_gclient::*; #[tokio::test] #[ignore] async fn create_test() -> Result<()> { let api = GearApi::dev_from_path("target/tmp/gear").await?; + //let api = GearApi::dev().await?; let mut listener = api.subscribe().await?; // Subscribing for events. assert!(listener.blocks_running().await?); @@ -27,21 +27,19 @@ async fn create_test() -> Result<()> { .expect("Error add new collection"); assert!(listener.message_processed(message_id).await?.succeed()); - let message_id = create_collection(&api, program_id) + let (message_id, fee) = create_collection(&api, program_id) .await .expect("Error create collection"); assert!(listener.message_processed(message_id).await?.succeed()); // Check marketplace balance let marketplace_balance = api.total_balance(program_id).await?; - assert_eq!(marketplace_balance, 10_000_000_000_000, "Wrong value"); - + assert_eq!(marketplace_balance, fee, "Wrong value"); // Check that collection created let state = get_all_collection_state(&api, &program_id) .await .expect("Unexpected invalid state."); assert!(!state.is_empty(), "Collections shouldn't be empty"); - // get the address and ProgramId of the created collection let address_nft = state[0].0; let address_nft_list = address_nft.as_ref(); @@ -53,20 +51,28 @@ async fn create_test() -> Result<()> { .expect("Unexpected invalid state."); assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); - let percent_to_marketplace = 10_000_000_000_000 * royalty_to_marketplace as u128 / 10_000; - let payment_for_mint = 10_000_000_000_000 + percent_to_marketplace; + let percent_to_marketplace = 10_000_000_000_000 * 200 as u128 / 10_000; + let payment_for_mint = 10_000_000_000_000 + percent_to_marketplace ; + let message_id = mint(&api, program_id, address_nft, payment_for_mint) .await .expect("Error mint"); assert!(listener.message_processed(message_id).await?.succeed()); - // Check marketplace balance + // Check marketplace and collection owner balance let marketplace_balance_after_mint = api.total_balance(program_id).await?; assert_eq!( marketplace_balance_after_mint, marketplace_balance + percent_to_marketplace, "Wrong value" ); + + let collection_owner_balance = api.total_balance(get_program_id_from_u64(USERS[0])).await?; + assert_eq!( + collection_owner_balance, + 10_000_000_000_000, + "Wrong value" + ); // Check success of mint let state = get_all_state_nft(&api, &nft_pid) @@ -74,21 +80,21 @@ async fn create_test() -> Result<()> { .expect("Unexpected invalid state."); assert!(!state.tokens.is_empty()); - assert_eq!(state.img_links_and_data.len(), 9); - + // assert_eq!(state.img_links_and_data.len(), 9); + + assert_eq!(state.img_links_and_data.len(), 9999); Ok(()) } #[tokio::test] -#[serial] #[ignore] async fn sale_test() -> Result<()> { let api = GearApi::dev_from_path("target/tmp/gear").await?; - let mut listener = api.subscribe().await?; // Subscribing for events. assert!(listener.blocks_running().await?); let royalty_to_marketplace = 200; + let royalty_to_collection_owner = 1000; let (message_id, program_id) = init_marketplace(&api) .await @@ -100,14 +106,14 @@ async fn sale_test() -> Result<()> { .expect("Error add new collection"); assert!(listener.message_processed(message_id).await?.succeed()); - let message_id = create_collection(&api, program_id) + let (message_id, fee) = create_collection(&api, program_id) .await .expect("Error create collection"); assert!(listener.message_processed(message_id).await?.succeed()); // Check marketplace balance let marketplace_balance = api.total_balance(program_id).await?; - assert_eq!(marketplace_balance, 10_000_000_000_000, "Wrong value"); + assert_eq!(marketplace_balance, fee, "Wrong value"); // Check that collection created let state = get_all_collection_state(&api, &program_id) @@ -126,27 +132,33 @@ async fn sale_test() -> Result<()> { .expect("Unexpected invalid state."); assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); - let percent_to_marketplace = 10_000_000_000_000 * royalty_to_marketplace as u128 / 10_000; - let payment_for_mint = 10_000_000_000_000 + percent_to_marketplace; + let percent_to_marketplace = 10_000_000_000_000 * 200 as u128 / 10_000; + let payment_for_mint = 10_000_000_000_000 + percent_to_marketplace ; let message_id = mint(&api, program_id, address_nft, payment_for_mint) .await .expect("Error mint"); assert!(listener.message_processed(message_id).await?.succeed()); - // Check marketplace balance + // Check marketplace and collection owner balance let marketplace_balance_after_mint = api.total_balance(program_id).await?; assert_eq!( marketplace_balance_after_mint, marketplace_balance + percent_to_marketplace, "Wrong value" ); + let collection_owner_balance = api.total_balance(get_program_id_from_u64(USERS[0])).await?; + assert_eq!( + collection_owner_balance, + 10_000_000_000_000, + "Wrong value" + ); // Check success of mint let state = get_all_state_nft(&api, &nft_pid) .await .expect("Unexpected invalid state."); assert!(!state.tokens.is_empty()); - assert_eq!(state.img_links_and_data.len(), 9); + assert_eq!(state.img_links_and_data.len(), 9999); // Approve to marketplace let message_id = approve(&api, program_id, nft_pid) @@ -156,7 +168,7 @@ async fn sale_test() -> Result<()> { let price = 150_000_000_000_000; let percent_to_marketplace = price * royalty_to_marketplace as u128 / 10_000; - + let percent_to_collection_owner = price * royalty_to_collection_owner as u128 / 10_000; // Sale let sale_payload = NftMarketplaceAction::SaleNft { collection_address: address_nft, @@ -174,7 +186,6 @@ async fn sale_test() -> Result<()> { let client = get_new_client(&api, USERS_STR[0]).await; let mut client_listener = client.subscribe().await?; - // Buy let buy_payload = NftMarketplaceAction::BuyNft { collection_address: address_nft, @@ -189,7 +200,6 @@ async fn sale_test() -> Result<()> { true, ) .await?; - let (message_id, _) = client .send_message( program_id, @@ -212,13 +222,19 @@ async fn sale_test() -> Result<()> { assert_eq!(token.0, 0); assert_eq!(token.1.owner, api.get_specific_actor_id(USERS_STR[0])); - // check balance of marketplace + // check balance of marketplace and collection owner let marketplace_balance_after_sale = api.total_balance(program_id).await?; assert_eq!( marketplace_balance_after_sale, marketplace_balance_after_mint + percent_to_marketplace, "Wrong value" ); + let collection_owner_balance = api.total_balance(get_program_id_from_u64(USERS[0])).await?; + assert_eq!( + collection_owner_balance, + 10_000_000_000_000+percent_to_collection_owner, + "Wrong value" + ); Ok(()) } @@ -227,11 +243,11 @@ async fn sale_test() -> Result<()> { #[ignore] async fn auction_test() -> Result<()> { let api = GearApi::dev_from_path("target/tmp/gear").await?; - let mut listener = api.subscribe().await?; // Subscribing for events. assert!(listener.blocks_running().await?); let royalty_to_marketplace = 200; + let royalty_to_collection_owner = 1000; let (message_id, program_id) = init_marketplace(&api) .await @@ -243,14 +259,14 @@ async fn auction_test() -> Result<()> { .expect("Error add new collection"); assert!(listener.message_processed(message_id).await?.succeed()); - let message_id = create_collection(&api, program_id) + let (message_id, fee) = create_collection(&api, program_id) .await .expect("Error create collection"); assert!(listener.message_processed(message_id).await?.succeed()); // Check marketplace balance let marketplace_balance = api.total_balance(program_id).await?; - assert_eq!(marketplace_balance, 10_000_000_000_000, "Wrong value"); + assert_eq!(marketplace_balance, fee, "Wrong value"); // Check that collection created let state = get_all_collection_state(&api, &program_id) @@ -269,20 +285,26 @@ async fn auction_test() -> Result<()> { .expect("Unexpected invalid state."); assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); - let percent_to_marketplace = 10_000_000_000_000 * royalty_to_marketplace as u128 / 10_000; - let payment_for_mint = 10_000_000_000_000 + percent_to_marketplace; + let percent_to_marketplace = 10_000_000_000_000 * 200 as u128 / 10_000; + let payment_for_mint = 10_000_000_000_000 + percent_to_marketplace ; let message_id = mint(&api, program_id, address_nft, payment_for_mint) .await .expect("Error mint"); assert!(listener.message_processed(message_id).await?.succeed()); - // Check marketplace balance + // Check marketplace and collection owner balance let marketplace_balance_after_mint = api.total_balance(program_id).await?; assert_eq!( marketplace_balance_after_mint, marketplace_balance + percent_to_marketplace, "Wrong value" ); + let collection_owner_balance = api.total_balance(get_program_id_from_u64(USERS[0])).await?; + assert_eq!( + collection_owner_balance, + 10_000_000_000_000, + "Wrong value" + ); // Check success of mint let state = get_all_state_nft(&api, &nft_pid) @@ -290,7 +312,7 @@ async fn auction_test() -> Result<()> { .expect("Unexpected invalid state."); assert!(!state.tokens.is_empty()); - assert_eq!(state.img_links_and_data.len(), 9); + assert_eq!(state.img_links_and_data.len(), 9999); // Approve to marketplace let message_id = approve(&api, program_id, nft_pid) @@ -299,8 +321,8 @@ async fn auction_test() -> Result<()> { assert!(listener.message_processed(message_id).await?.succeed()); // Create auction - let duration_in_block = 12; - let duration_in_secs = 36; // 36 secs + let duration_in_block = 20; + let duration_in_secs = 60; // 60 secs let create_auction_payload = NftMarketplaceAction::CreateAuction { collection_address: address_nft, token_id: 0, @@ -311,11 +333,13 @@ async fn auction_test() -> Result<()> { let gas_info = api .calculate_handle_gas(None, program_id, create_auction_payload.encode(), 0, true) .await?; + + println!("Gas INFo: {:?}", gas_info); + let (message_id, _) = api .send_message(program_id, create_auction_payload, gas_info.min_limit, 0) .await?; - let start = Instant::now(); assert!(listener.message_processed(message_id).await?.succeed()); // Check state @@ -325,29 +349,80 @@ async fn auction_test() -> Result<()> { assert!(!state.auctions.is_empty()); // Add bid - let client = get_new_client(&api, USERS_STR[0]).await; - let mut client_listener = client.subscribe().await?; + let client_1 = get_new_client(&api, USERS_STR[0]).await; + let mut client_listener = client_1.subscribe().await?; let add_bid_payload = NftMarketplaceAction::AddBid { collection_address: address_nft, token_id: 0, }; - let bid = 150_000_000_000_000; - let percent_to_marketplace = bid * royalty_to_marketplace as u128 / 10_000; + let bid_1 = 150_000_000_000_000; - let gas_info = api - .calculate_handle_gas(None, program_id, add_bid_payload.encode(), bid, true) + let gas_info = client_1 + .calculate_handle_gas(None, program_id, add_bid_payload.encode(), bid_1, true) .await?; - let (message_id, _) = client - .send_message(program_id, add_bid_payload, gas_info.min_limit, bid) + let (message_id, _) = client_1 + .send_message(program_id, add_bid_payload, gas_info.min_limit, bid_1) + .await?; + assert!(client_listener + .message_processed(message_id) + .await? + .succeed()); + + let state = get_marketplace_state(&api, &program_id) + .await + .expect("Unexpected invalid state."); + let user_0 = client_1.get_specific_actor_id(USERS_STR[0]); + assert_eq!(state.auctions[0].1.current_winner, user_0); + + let user_0: [u8; 32] = user_0.into(); + let user_0: ProgramId = user_0.into(); + let balance_user_0 = api.total_balance(user_0).await?; + + // Add bid + let client_2 = get_new_client(&api, USERS_STR[1]).await; + let mut client_listener = client_2.subscribe().await?; + let add_bid_payload = NftMarketplaceAction::AddBid { + collection_address: address_nft, + token_id: 0, + }; + + let bid_2 = 200_000_000_000_000; + let percent_to_marketplace = bid_2 * royalty_to_marketplace as u128 / 10_000; + let percent_to_collection_owner = bid_2 * royalty_to_collection_owner as u128 / 10_000; + let gas_info = client_2 + .calculate_handle_gas(None, program_id, add_bid_payload.encode(), bid_2, true) + .await?; + let (message_id, _) = client_2 + .send_message(program_id, add_bid_payload, gas_info.min_limit, bid_2) .await?; assert!(client_listener .message_processed(message_id) .await? .succeed()); - let duration = start.elapsed().as_secs(); - std::thread::sleep(std::time::Duration::from_secs(duration_in_secs - duration)); + let state = get_marketplace_state(&api, &program_id) + .await + .expect("Unexpected invalid state."); + assert_eq!(state.auctions[0].1.current_winner, client_2.get_specific_actor_id(USERS_STR[1])); + + let balance_user_0_new = api.total_balance(user_0).await?; + assert_eq!( + balance_user_0_new - balance_user_0, + 150_000_000_000_000, + "Wrong value" + ); + + let state = get_marketplace_state(&api, &program_id) + .await + .expect("Unexpected invalid state."); + println!("STATE: {:?}", state); + + std::thread::sleep(std::time::Duration::from_secs(duration_in_secs)); + let state = get_marketplace_state(&api, &program_id) + .await + .expect("Unexpected invalid state."); + println!("STATE: {:?}", state); // Check success of close auction let state = get_all_state_nft(&api, &nft_pid) @@ -356,16 +431,21 @@ async fn auction_test() -> Result<()> { let token = state.tokens.get(0).expect("Can't be None"); assert_eq!(token.0, 0); - assert_eq!(token.1.owner, api.get_specific_actor_id(USERS_STR[0])); + assert_eq!(token.1.owner, api.get_specific_actor_id(USERS_STR[1])); - // Check marketplace balance + // Check marketplace and collection owner balance let marketplace_balance_after_auction = api.total_balance(program_id).await?; assert_eq!( marketplace_balance_after_auction, marketplace_balance_after_mint + percent_to_marketplace, "Wrong value" ); - + let collection_owner_balance = api.total_balance(get_program_id_from_u64(USERS[1])).await?; + assert_eq!( + collection_owner_balance, + 10_000_000_000_000+percent_to_collection_owner, + "Wrong value" + ); Ok(()) } @@ -373,11 +453,11 @@ async fn auction_test() -> Result<()> { #[ignore] async fn offer_test() -> Result<()> { let api = GearApi::dev_from_path("target/tmp/gear").await?; - let mut listener = api.subscribe().await?; // Subscribing for events. assert!(listener.blocks_running().await?); let royalty_to_marketplace = 200; + let royalty_to_collection_owner = 1000; let (message_id, program_id) = init_marketplace(&api) .await @@ -389,14 +469,14 @@ async fn offer_test() -> Result<()> { .expect("Error add new collection"); assert!(listener.message_processed(message_id).await?.succeed()); - let message_id = create_collection(&api, program_id) + let (message_id, fee) = create_collection(&api, program_id) .await .expect("Error create collection"); assert!(listener.message_processed(message_id).await?.succeed()); // Check marketplace balance let marketplace_balance = api.total_balance(program_id).await?; - assert_eq!(marketplace_balance, 10_000_000_000_000, "Wrong value"); + assert_eq!(marketplace_balance, fee, "Wrong value"); // Check that collection created let state = get_all_collection_state(&api, &program_id) @@ -415,20 +495,26 @@ async fn offer_test() -> Result<()> { .expect("Unexpected invalid state."); assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); - let percent_to_marketplace = 10_000_000_000_000 * royalty_to_marketplace as u128 / 10_000; - let payment_for_mint = 10_000_000_000_000 + percent_to_marketplace; + let percent_to_marketplace = 10_000_000_000_000 * 200 as u128 / 10_000; + let payment_for_mint = 10_000_000_000_000 + percent_to_marketplace ; let message_id = mint(&api, program_id, address_nft, payment_for_mint) .await .expect("Error mint"); assert!(listener.message_processed(message_id).await?.succeed()); - // Check marketplace balance + // Check marketplace balance and collection owner balance let marketplace_balance_after_mint = api.total_balance(program_id).await?; assert_eq!( marketplace_balance_after_mint, marketplace_balance + percent_to_marketplace, "Wrong value" ); + let collection_owner_balance = api.total_balance(get_program_id_from_u64(USERS[0])).await?; + assert_eq!( + collection_owner_balance, + 10_000_000_000_000, + "Wrong value" + ); // Check success of mint let state = get_all_state_nft(&api, &nft_pid) @@ -436,7 +522,7 @@ async fn offer_test() -> Result<()> { .expect("Unexpected invalid state."); assert!(!state.tokens.is_empty()); - assert_eq!(state.img_links_and_data.len(), 9); + assert_eq!(state.img_links_and_data.len(), 9999); // Create offer let client = get_new_client(&api, USERS_STR[0]).await; @@ -448,6 +534,8 @@ async fn offer_test() -> Result<()> { let offer_price = 150_000_000_000_000; let percent_to_marketplace = offer_price * royalty_to_marketplace as u128 / 10_000; + let percent_to_collection_owner = offer_price * royalty_to_collection_owner as u128 / 10_000; + let gas_info = client .calculate_handle_gas( @@ -513,13 +601,85 @@ async fn offer_test() -> Result<()> { assert_eq!(token.0, 0); assert_eq!(token.1.owner, api.get_specific_actor_id(USERS_STR[0])); - // Check balance + // Check marketplace and collection owner balance let marketplace_balance_after_offer = api.total_balance(program_id).await?; assert_eq!( marketplace_balance_after_offer, marketplace_balance_after_mint + percent_to_marketplace, "Wrong value" ); + let collection_owner_balance = api.total_balance(get_program_id_from_u64(USERS[0])).await?; + assert_eq!( + collection_owner_balance, + 10_000_000_000_000+percent_to_collection_owner, + "Wrong value" + ); Ok(()) } + + +// #[tokio::test] +// #[ignore] +// async fn nft_init() -> Result<()> { +// let api = GearApi::dev().await?; +// let mut listener = api.subscribe().await?; // Subscribing for events. +// assert!(listener.blocks_running().await?); + +// let img_data = ImageData { +// limit_copies: Some(1), +// }; +// let number = 69_500; +// let fee = number*257_142_857_100; + +// let img_links_and_data: Vec<(String, ImageData)> = (0..number) +// .map(|i| (format!("Img-{}", i), img_data.clone())) +// .collect(); + +// let init_nft_payload = NftInit { +// collection_owner: USERS[0].into(), +// config: NftConfig { +// name: "User Collection".to_string(), +// description: "User Collection".to_string(), +// collection_banner: "Collection banner".to_string(), +// collection_logo: "Collection logo".to_string(), +// collection_tags: vec!["tag1".to_string()], +// additional_links: None, +// royalty: 1_000, +// user_mint_limit: Some(3), +// payment_for_mint: 10_000_000_000_000, +// transferable: Some(0), +// sellable: Some(0), +// }, +// img_links_and_data, +// permission_to_mint: None, +// } +// .encode(); + +// let path = "target/wasm32-unknown-unknown/debug/nft.opt.wasm"; + +// let gas_info = api +// .calculate_upload_gas( +// None, +// gclient::code_from_os(path).unwrap(), +// init_nft_payload.clone(), +// fee, +// true, +// ) +// .await +// .expect("Error calculate upload gas"); + +// println!("GAS INFO {:?}", gas_info); + +// let (message_id, program_id, _) = api +// .upload_program_bytes( +// gclient::code_from_os(path).unwrap(), +// gclient::now_micros().to_le_bytes(), +// init_nft_payload, +// gas_info.min_limit, +// fee, +// ) +// .await +// .expect("Error upload program bytes"); +// Ok(()) +// } diff --git a/tests/test_marketplace.rs b/tests/test_marketplace.rs index 0bfe249..78ada2d 100644 --- a/tests/test_marketplace.rs +++ b/tests/test_marketplace.rs @@ -78,8 +78,6 @@ fn create_success() { None, None, None, - Some(11_000_000_000_000), - None, None, ); let state_reply = marketplace @@ -192,8 +190,6 @@ fn create_failures() { None, None, None, - Some(11_000_000_000_000), - None, Some(NftMarketplaceError::AccessDenied), ); @@ -443,7 +439,7 @@ fn sale_failures() { address_nft, 0, 9_000_000_000_000, - Some(NftMarketplaceError::LessThanExistentialDeposit), + Some(NftMarketplaceError::LessThanMinimumValueForTrade), ); // Only owner can send this action @@ -662,7 +658,7 @@ fn auction_success() { println!("STATE: {:?}", state); } - sys.spend_blocks(duration); + sys.spend_blocks(duration+1); let percent_to_collection_owner = final_bid * royalty as u128 / 10_000; let percent_to_marketplace = final_bid * 200 as u128 / 10_000; @@ -844,7 +840,7 @@ fn auction_cancel() { assert!(!state.auctions.is_empty()); } - sys.spend_blocks(duration_1); + sys.spend_blocks(duration_1+1); // the delayed message from the first version of the auction will come, // but it should end with an error, because the auction was canceled and a new one was created. @@ -946,7 +942,7 @@ fn auction_failures() { 0, 9_000_000_000_000, duration, - Some(NftMarketplaceError::LessThanExistentialDeposit), + Some(NftMarketplaceError::LessThanMinimumValueForTrade), ); // Only token owner can send @@ -1014,7 +1010,7 @@ fn auction_failures() { let balance = sys.balance_of(USERS[2]); assert_eq!(balance, 20_000_000_000_000, "Wrong balance"); - sys.spend_blocks(duration); + sys.spend_blocks(duration+1); sys.mint_to(USERS[3], 15_000_000_000_000); marketplace.add_bid( USERS[3], diff --git a/tests/test_nft.rs b/tests/test_nft.rs index 1aa15a5..b1e4122 100644 --- a/tests/test_nft.rs +++ b/tests/test_nft.rs @@ -2,8 +2,7 @@ use crate::utils::*; use utils::prelude::*; mod utils; use gtest::Program; -use nft_io::{ - Action, AdditionalLinks, Config, ImageData, NftAction, NftError, NftInit, NftState, +use nft_io::{AdditionalLinks, Config, ImageData, NftAction, NftError, NftInit, NftState, StateQuery as StateQueryNft, StateReply as StateReplyNft, }; use nft_marketplace_io::*; @@ -183,7 +182,6 @@ fn successful_basics() { // Successful Expand NFT in the collection let img_data = ImageData { limit_copies: Some(1), - auto_changing_rules: None, }; let res = nft_collection.send( USERS[0], @@ -255,7 +253,6 @@ fn failures() { // Limit of copies value is equal to 0 let img_data = ImageData { limit_copies: Some(0), - auto_changing_rules: None, }; init_nft_payload.img_links_and_data = vec![("Img-0".to_owned(), img_data.clone())]; marketplace.create_collection( @@ -271,7 +268,6 @@ fn failures() { let img_data = ImageData { limit_copies: Some(1), - auto_changing_rules: None, }; let img_links_and_data: Vec<(String, ImageData)> = (0..5) .map(|i| (format!("Img-{}", i), img_data.clone())) @@ -342,7 +338,6 @@ fn failures() { let img_data = ImageData { limit_copies: Some(4), - auto_changing_rules: None, }; let res = nft_collection.send( USERS[0], @@ -393,141 +388,6 @@ fn failures() { check_nft_error(USERS[3], &res, NftError::AccessDenied); } -#[test] -fn check_auto_changing_rules() { - let sys = utils::initialize_system(); - let marketplace = Program::init_marketplace(&sys); - let nft_collection_code_id = - sys.submit_code("target/wasm32-unknown-unknown/debug/nft.opt.wasm"); - - let state_reply = marketplace - .read_state(StateQuery::CollectionsInfo) - .expect("Unexpected invalid state."); - if let StateReply::CollectionsInfo(state) = state_reply { - assert!(state.is_empty(), "Collection info should be empty"); - println!("Collection info: {:?}", state); - } - // Successful addition of a new collection - let name_simple_nft = "Simple NFT".to_string(); - marketplace.add_new_collection( - ADMINS[0], - nft_collection_code_id.into_bytes().into(), - name_simple_nft.clone(), - None, - ); - - let state_reply = marketplace - .read_state(StateQuery::CollectionsInfo) - .expect("Unexpected invalid state."); - if let StateReply::CollectionsInfo(state) = state_reply { - assert!(!state.is_empty(), "Collection info shouldn't be empty"); - println!("Collection info: {:?}", state); - } - let img_data = ImageData { - limit_copies: Some(1), - auto_changing_rules: Some(vec![ - (9, Action::ChangeImg("Auto change image".to_string())), - (18, Action::AddMeta("Auto change metadata".to_string())), - ]), - }; - let img_links_and_data: Vec<(String, ImageData)> = (0..10) - .map(|i| (format!("Img-{}", i), img_data.clone())) - .collect(); - - let additional_links = Some(AdditionalLinks { - external_url: Some("External link".to_string()), - telegram: None, - xcom: None, - medium: None, - discord: None, - }); - - // Successful creation of a new collection - let init_nft_payload = NftInit { - collection_owner: USERS[0].into(), - config: Config { - name: "User Collection".to_string(), - description: "User Collection".to_string(), - collection_banner: "Collection banner".to_string(), - collection_logo: "Collection logo".to_string(), - collection_tags: vec!["tag1".to_string()], - additional_links, - royalty: 0, - user_mint_limit: 3.into(), - payment_for_mint: 0, - transferable: Some(0), - sellable: Some(0), - }, - img_links_and_data, - permission_to_mint: None, - fee_per_uploaded_file: 257_142_857_100, - }; - sys.mint_to(USERS[0], 100_000_000_000_000); - marketplace.create_collection( - USERS[0], - name_simple_nft.clone(), - init_nft_payload.encode(), - 10_000_000_000_000, - None, - ); - let state_reply = marketplace - .read_state(StateQuery::AllCollections) - .expect("Unexpected invalid state."); - let address_nft = if let StateReply::AllCollections(state) = state_reply { - assert!(!state.is_empty(), "Collections shouldn't be empty"); - println!("Collections: {:?}", state); - state[0].0 - } else { - assert!(false, "Unexpected StateReply variant"); - 0.into() - }; - - let address_nft_list: [u8; 32] = address_nft.into(); - let nft_collection = sys.get_program(address_nft_list); - let state_reply = nft_collection - .read_state(StateQueryNft::All) - .expect("Unexpected invalid state."); - if let StateReplyNft::All(state) = state_reply { - assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); - println!("Collection NFT info: {:?}", state); - } - - // Successful mint NFT in the new collection - marketplace.mint(USERS[1], address_nft, None); - - let state_reply = nft_collection - .read_state(StateQueryNft::All) - .expect("Unexpected invalid state."); - if let StateReplyNft::All(state) = state_reply { - println!("Collection NFT info: {:?}", state); - assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); - let (owner, token_id) = state.owners.get(0).expect("Can't be None"); - assert_eq!(*owner, USERS[1].into(), "Wrong owner"); - assert_eq!(*token_id, vec![0], "Wrong token id"); - } - - let state = get_state(&nft_collection).unwrap(); - assert_ne!(state.tokens[0].1.media_url, "Auto change image".to_string()); - assert_ne!( - state.tokens[0].1.metadata, - vec!["Auto change metadata".to_string()] - ); - sys.spend_blocks(3); - let state = get_state(&nft_collection).unwrap(); - assert_eq!(state.tokens[0].1.media_url, "Auto change image".to_string()); - assert_ne!( - state.tokens[0].1.metadata, - vec!["Auto change metadata".to_string()] - ); - sys.spend_blocks(6); - let state = get_state(&nft_collection).unwrap(); - assert_eq!(state.tokens[0].1.media_url, "Auto change image".to_string()); - assert_eq!( - state.tokens[0].1.metadata, - vec!["Auto change metadata".to_string()] - ); -} - #[test] fn check_transferable() { let sys = utils::initialize_system(); @@ -660,7 +520,7 @@ fn check_payment_for_mint() { assert_eq!(balance, 100_000_000_000_000, "Wrong balance"); // Successful creation of a new collection - let payment_for_mint = 11_000_000_000_000; + let payment_for_mint = 10_000_000_000_000; init_nft_payload.config.payment_for_mint = payment_for_mint; marketplace.create_collection( USERS[0], @@ -692,14 +552,15 @@ fn check_payment_for_mint() { println!("Collection NFT info: {:?}", state); } - // Successful mint NFT in the new collection sys.mint_to(USERS[1], 3 * payment_for_mint); + let payment_for_mint_with_percent = payment_for_mint + payment_for_mint * 200 as u128 / 10_000; + // Successful mint NFT in the new collection let res = marketplace.send_with_value( USERS[1], NftMarketplaceAction::Mint { collection_address: address_nft, }, - payment_for_mint + payment_for_mint * 200 / 10_000, + payment_for_mint_with_percent, ); assert!(!res.main_failed()); @@ -707,6 +568,8 @@ fn check_payment_for_mint() { sys.claim_value_from_mailbox(USERS[0]); let balance = sys.balance_of(USERS[0]); assert_eq!(balance - old_balance, payment_for_mint, "Wrong balance"); + + // Wrong Value let res = marketplace.send_with_value( USERS[1], NftMarketplaceAction::Mint { @@ -719,7 +582,6 @@ fn check_payment_for_mint() { println!("RES: {:?}", result); assert!(!res.main_failed()); - assert!(res.contains(&( USERS[1], Err::(NftMarketplaceError::WrongValue).encode() diff --git a/tests/utils/mod.rs b/tests/utils/mod.rs index 63095e3..99ae554 100644 --- a/tests/utils/mod.rs +++ b/tests/utils/mod.rs @@ -104,10 +104,8 @@ pub trait NftMarketplace { gas_for_delete_collection: Option, gas_for_get_info: Option, time_between_create_collections: Option, - fee_per_uploaded_file: Option, royalty_to_marketplace_for_trade: Option, royalty_to_marketplace_for_mint: Option, - minimum_transfer_value: Option, ms_in_block: Option, error: Option, ); @@ -124,18 +122,19 @@ impl NftMarketplace for Program<'_> { fn init_marketplace(sys: &System) -> Program<'_> { let marketplace = Program::current(sys); let init_payload = NftMarketplaceInit { - gas_for_creation: 1_000_000_000_000_000, - gas_for_mint: 100_000_000_000, - gas_for_transfer_token: 5_000_000_000, - gas_for_close_auction: 10_000_000_000, - gas_for_delete_collection: 5_000_000_000, + gas_for_creation: 5_000_000_000, + gas_for_mint: 7_000_000_000, + gas_for_transfer_token: 4_000_000_000, + gas_for_close_auction: 15_000_000_000, + gas_for_delete_collection: 3_000_000_000, gas_for_get_info: 5_000_000_000, time_between_create_collections: 3_600_000, // 1 hour in milliseconds - fee_per_uploaded_file: 257_142_857_100, royalty_to_marketplace_for_trade: 200, royalty_to_marketplace_for_mint: 200, - minimum_transfer_value: 10_300_000_000_000, // because roylty to marketplace ms_in_block: 3_000, + fee_per_uploaded_file: 257_142_857_100, + max_creator_royalty: 1_000, + max_number_of_images: 10_000, }; let res = marketplace.send(ADMINS[0], init_payload); assert!(!res.main_failed()); @@ -289,7 +288,7 @@ impl NftMarketplace for Program<'_> { collection_address, token_id, min_price, - duration, + duration_ms: (duration+1) * 3000, }) }; let result = &res.decoded_log::>(); @@ -460,10 +459,8 @@ impl NftMarketplace for Program<'_> { gas_for_delete_collection: Option, gas_for_get_info: Option, time_between_create_collections: Option, - fee_per_uploaded_file: Option, royalty_to_marketplace_for_trade: Option, royalty_to_marketplace_for_mint: Option, - minimum_transfer_value: Option, ms_in_block: Option, error: Option, ) { @@ -477,11 +474,12 @@ impl NftMarketplace for Program<'_> { gas_for_delete_collection, gas_for_get_info, time_between_create_collections, - fee_per_uploaded_file, royalty_to_marketplace_for_trade, royalty_to_marketplace_for_mint, - minimum_transfer_value, ms_in_block, + fee_per_uploaded_file: None, + max_creator_royalty: None, + max_number_of_images: None }, ); assert!(!res.main_failed()); @@ -534,7 +532,6 @@ pub fn get_init_nft_payload( ) -> NftInit { let img_data = ImageData { limit_copies: Some(1), - auto_changing_rules: None, }; let img_links_and_data: Vec<(String, ImageData)> = (0..10) .map(|i| (format!("Img-{}", i), img_data.clone())) @@ -557,7 +554,6 @@ pub fn get_init_nft_payload( }, img_links_and_data, permission_to_mint, - fee_per_uploaded_file: 257_142_857_100, } } // pub fn get_state( diff --git a/tests/utils_gclient/mod.rs b/tests/utils_gclient/mod.rs index 7658b14..bca0141 100644 --- a/tests/utils_gclient/mod.rs +++ b/tests/utils_gclient/mod.rs @@ -79,18 +79,19 @@ pub async fn init_marketplace(api: &GearApi) -> Result<(MessageId, ProgramId), ( let royalty_to_marketplace = 200; let init_marketplace = NftMarketplaceInit { - gas_for_creation: 200_000_000_000, - gas_for_mint: 100_000_000_000, - gas_for_transfer_token: 5_000_000_000, - gas_for_close_auction: 10_000_000_000, - gas_for_delete_collection: 5_000_000_000, - gas_for_get_info: 5_000_000_000, + gas_for_creation: 110_000_000_000, + gas_for_mint: 10_000_000_000, // 7_000_000_000 + gas_for_transfer_token: 6_000_000_000, // 4_000_000_000 + gas_for_close_auction: 13_000_000_000, // 15_000_000_000 + gas_for_delete_collection: 5_000_000_000, // 3_000_000_000 + gas_for_get_info: 7_000_000_000, // 5_000_000_000 time_between_create_collections: 3_600_000, // 1 hour in milliseconds - fee_per_uploaded_file: 257_142_857_100, royalty_to_marketplace_for_trade: royalty_to_marketplace, royalty_to_marketplace_for_mint: royalty_to_marketplace, - minimum_transfer_value: 10_300_000_000_000, // because roylty to marketplace ms_in_block: 3_000, + fee_per_uploaded_file: 257_142_857_100, + max_creator_royalty: 1_000, + max_number_of_images: 10_000, } .encode(); @@ -165,12 +166,13 @@ pub async fn add_new_collection( pub async fn create_collection( api: &GearApi, marketplace_program_id: ProgramId, -) -> Result { +) -> Result<(MessageId, u128), ()> { let img_data = ImageData { limit_copies: Some(1), - auto_changing_rules: None, }; - let img_links_and_data: Vec<(String, ImageData)> = (0..10) + let number_of_image = 10_000; + let fee = number_of_image * 257_142_857_100; + let img_links_and_data: Vec<(String, ImageData)> = (0..number_of_image) .map(|i| (format!("Img-{}", i), img_data.clone())) .collect(); @@ -183,7 +185,7 @@ pub async fn create_collection( collection_logo: "Collection logo".to_string(), collection_tags: vec!["tag1".to_string()], additional_links: None, - royalty: 0, + royalty: 1_000, user_mint_limit: Some(3), payment_for_mint: 10_000_000_000_000, transferable: Some(0), @@ -191,7 +193,6 @@ pub async fn create_collection( }, img_links_and_data, permission_to_mint: None, - fee_per_uploaded_file: 257_142_857_100, } .encode(); @@ -204,23 +205,23 @@ pub async fn create_collection( None, marketplace_program_id, create_collection_payload.encode(), - 10_000_000_000_000, + fee, true, ) .await .expect("Error calculate gas"); - + println!("GAS INFO CREATE: {:?}", gas_info); let (message_id, _) = api .send_message( marketplace_program_id, create_collection_payload, gas_info.min_limit, - 10_000_000_000_000, + fee, ) .await .expect("Error send message"); - Ok(message_id) + Ok((message_id, fee)) } pub async fn mint( @@ -300,7 +301,7 @@ pub async fn get_new_client(api: &GearApi, name: &str) -> GearApi { .total_balance(api.account_id()) .await .expect("Error total balance"); - let amount = alice_balance / 10; + let amount = 1_000_000_000_000_000; api.transfer( api.get_specific_actor_id(name) .encode() @@ -313,6 +314,12 @@ pub async fn get_new_client(api: &GearApi, name: &str) -> GearApi { .expect("Error transfer"); api.clone() - .with(USERS_STR[0]) + .with(name) .expect("Unable to change signer.") } + +pub fn get_program_id_from_u64(u64: u64) -> ProgramId { + let actor_id: gstd::ActorId = USERS[0].into(); + let list: [u8; 32] = actor_id.into(); + list.into() +} From 346d936837338cec69ea82749864de1ab0f884ac Mon Sep 17 00:00:00 2001 From: MedovTimur Date: Mon, 18 Mar 2024 16:09:17 +0300 Subject: [PATCH 6/8] add restriction for create collectoin, update music nft --- io/src/lib.rs | 12 + music-nft/io/src/lib.rs | 91 +- music-nft/src/lib.rs | 545 ++++++------ nft/io/src/lib.rs | 1 + nft/src/lib.rs | 7 +- src/auction.rs | 19 +- src/lib.rs | 133 ++- src/nft_messages.rs | 5 +- src/offer.rs | 8 +- src/sale.rs | 8 +- tests/test_music_nft.rs | 1697 ++++++++++++++++-------------------- tests/utils/mod.rs | 53 ++ tests/utils_gclient/mod.rs | 53 +- 13 files changed, 1342 insertions(+), 1290 deletions(-) diff --git a/io/src/lib.rs b/io/src/lib.rs index 2f02096..6976190 100644 --- a/io/src/lib.rs +++ b/io/src/lib.rs @@ -47,6 +47,7 @@ pub enum NftMarketplaceAction { meta_link: String, type_name: String, type_description: String, + allow_create: bool, }, CreateCollection { type_name: String, @@ -121,6 +122,12 @@ pub enum NftMarketplaceAction { max_creator_royalty: Option, max_number_of_images: Option, }, + AllowMessage(bool), + AllowCreateCollection(bool), + AddExternalCollection { + collection_address: ActorId, + type_name: String, + }, } #[derive(Encode, Decode, Debug, TypeInfo)] @@ -226,6 +233,8 @@ pub enum NftMarketplaceEvent { value: u128, }, ValueSent, + AllowMessageChanged, + AllowCreateCollectionChanged } #[derive(Debug, Clone, Encode, Decode, TypeInfo)] @@ -286,6 +295,8 @@ pub struct State { pub offers: Vec<(Offer, u128)>, pub config: Config, pub minimum_value_for_trade: u128, + pub allow_message: bool, + pub allow_create_collection: bool, } /// * code_id - code_id is used to create a collection by that CodeId, @@ -297,6 +308,7 @@ pub struct TypeCollectionInfo { pub code_id: CodeId, pub meta_link: String, pub type_description: String, + pub allow_create: bool, } #[derive(Debug, Encode, Decode, TypeInfo, Clone)] diff --git a/music-nft/io/src/lib.rs b/music-nft/io/src/lib.rs index 3e92c57..74003df 100644 --- a/music-nft/io/src/lib.rs +++ b/music-nft/io/src/lib.rs @@ -5,9 +5,8 @@ use gstd::{prelude::*, ActorId}; pub type NftId = u64; pub type TimeSec = u32; -pub const BLOCK_DURATION_IN_SECS: u32 = 3; -pub const EXISTENTIAL_DEPOSIT: u128 = 10_000_000_000_000; -pub const GAS_AUTO_CHANGING: u64 = 5_000_000_000; +pub const FEE_PER_UPLOADED_FILE: u128 = 282_857_142_900; +pub const MAX_CREATOR_ROYALTY: u16 = 1_000; // 10% pub struct ContractMetadata; @@ -25,19 +24,13 @@ pub struct MusicNftInit { pub collection_owner: ActorId, pub config: Config, pub links_and_data: Vec<(Links, ImageData)>, + pub permission_to_mint: Option>, } #[derive(Debug, Clone, Encode, Decode, TypeInfo)] pub struct ImageData { pub limit_copies: Option, pub description: Option, - pub auto_changing_rules: Option>, -} - -#[derive(Debug, Clone, Encode, Decode, TypeInfo)] -pub enum Action { - ChangeImg(String), - AddMeta(String), } #[derive(Debug, Clone, Encode, Decode, TypeInfo)] @@ -87,7 +80,10 @@ pub enum MusicNftAction { token_id: NftId, }, CanDelete, - Mint, + GetPaymentForMint, + Mint { + minter: ActorId, + }, Approve { to: ActorId, token_id: NftId, @@ -101,14 +97,20 @@ pub enum MusicNftAction { ChangeConfig { config: Config, }, - ChangeImg { - token_id: NftId, - img_link: String, + AddUsersForMint { + users: Vec, }, - AddMetadata { - token_id: NftId, - metadata: String, + DeleteUserForMint { + user: ActorId, }, + LiftRestrictionMint, + AddAdmin { + admin: ActorId, + }, + RemoveAdmin { + admin: ActorId, + }, + } #[derive(Debug, Clone, Encode, Decode, TypeInfo)] @@ -126,16 +128,21 @@ pub enum MusicNftEvent { royalty: u16, }, CanDelete(bool), - Initialized { - config: Config, - total_number_of_tokens: Option, + PaymentForMintReceived { + payment_for_mint: u128, }, + SuccessfullyMinted, Minted { token_id: NftId, links_and_data: Nft, }, + Initialized { + config: Config, + total_number_of_tokens: Option, + permission_to_mint: Option>, + }, Approved { - account: ActorId, + to: ActorId, token_id: NftId, }, ApprovalRevoked { @@ -148,17 +155,42 @@ pub enum MusicNftEvent { ConfigChanged { config: Config, }, - ImageChanged { - token_id: NftId, - img_link: String, + UsersForMintAdded { + users: Vec, }, - MetadataAdded { - token_id: NftId, - metadata: String, + UserForMintDeleted { + user: ActorId, + }, + LiftRestrictionMint, + AdminAdded { + admin: ActorId, }, + AdminRemoved { + admin: ActorId, + } } + #[derive(Debug, Clone, Encode, Decode, TypeInfo)] -pub struct MusicNftError(pub String); +pub enum MusicNftError { + MathOverflow, + ErrorGettingRandom, + TokenDoesNotExist, + AllTokensMinted, + OwnerDoesNotHaveNft, + AccessDenied, + NoApproval, + ThereIsApproval, + LimitIsZero, + ConfigCannotBeChanged, + WrongRoyalty, + NotTransferable, + UserRestrictionCannotBeChanged, + NoListOfRestriction, + ThereIsNoSuchUser, + ExhaustedLimit, + WrongValue, + OnlyOneAdminLeft +} #[derive(Debug, Clone, Encode, Decode, TypeInfo)] pub struct NftState { @@ -170,6 +202,9 @@ pub struct NftState { pub links_and_data: Vec<(Links, ImageData)>, pub collection_owner: ActorId, pub total_number_of_tokens: Option, + pub permission_to_mint: Option>, + pub marketplace_address: ActorId, + pub admins: Vec, } #[derive(Debug, Clone, Encode, Decode, TypeInfo)] diff --git a/music-nft/src/lib.rs b/music-nft/src/lib.rs index 3d94276..676ce7c 100644 --- a/music-nft/src/lib.rs +++ b/music-nft/src/lib.rs @@ -17,73 +17,60 @@ struct NftContract { pub nonce: NftId, pub links_and_data: Vec<(Links, ImageData)>, pub collection_owner: ActorId, + pub permission_to_mint: Option>, pub total_number_of_tokens: Option, + pub marketplace_address: ActorId, + pub admins: HashSet, } static mut NFT_CONTRACT: Option = None; impl NftContract { - fn mint(&mut self) -> Result { - let msg_src = msg::source(); + fn mint(&mut self, minter: ActorId, msg_value: u128) -> Result { + // check if there are tokens for mint self.check_available_amount_of_tokens()?; - self.check_mint_limit(&msg_src)?; + + // check if a user can make a mint: + // - quantity limit + // - user-specific limit + self.check_mint(&minter)?; let Some(next_nft_nonce) = self.nonce.checked_add(1) else { - return Err(MusicNftError("Math overflow.".to_owned())); + return Err(MusicNftError::MathOverflow); }; - self.payment_for_mint()?; + // value check on mint + self.payment_for_mint(msg_value)?; let rand_index = get_random_value(self.links_and_data.len() as u64); - let music_link: String; - let mut img_link = self.config.collection_logo.clone(); - let mut token_description = self.config.description.clone(); let token_id = self.nonce; - if let Some((links, img_info)) = self.links_and_data.get_mut(rand_index as usize) { - if let Some(limit) = img_info.limit_copies.as_mut() { - *limit -= 1; - } - music_link = links.music_link.clone(); + let (links, img_info) = self + .links_and_data + .get_mut(rand_index as usize) + .ok_or(MusicNftError::ErrorGettingRandom)?; - if let Some(description) = &img_info.description { - token_description = description.clone(); - } - if let Some(image) = links.img_link.clone() { - img_link = image; - } + if let Some(limit) = img_info.limit_copies.as_mut() { + *limit -= 1; + } - if let Some(auto_changing_rules) = &img_info.auto_changing_rules { - auto_changing_rules - .iter() - .for_each(|(trigger_time, auto_action)| { - let action = match auto_action { - Action::ChangeImg(img_link) => MusicNftAction::ChangeImg { - token_id, - img_link: img_link.to_string(), - }, - Action::AddMeta(metadata) => MusicNftAction::AddMetadata { - token_id, - metadata: metadata.to_string(), - }, - }; - msg::send_with_gas_delayed( - exec::program_id(), - action, - GAS_AUTO_CHANGING, - 0, - *trigger_time / BLOCK_DURATION_IN_SECS, - ) - .expect("Error in sending a delayed message `NftAction`"); - }); - } + let img_link = if let Some(img_link) = &links.img_link { + img_link.clone() + } else { + self.config.collection_logo.clone() + }; - if let Some(0) = img_info.limit_copies { - self.links_and_data.remove(rand_index as usize); - } + let music_link = links.music_link.clone(); + let token_description = if let Some(token_description) = &img_info.description { + token_description.clone() } else { - return Err(MusicNftError("Error with getting a random nft".to_owned())); + self.config.description.clone() + }; + + // if there are 0 copies of this token left, then delete this token + if let Some(0) = img_info.limit_copies { + self.links_and_data.remove(rand_index as usize); } self.owners - .entry(msg_src) + .entry(minter) .and_modify(|ids| { ids.insert(token_id); }) @@ -91,7 +78,7 @@ impl NftContract { let name = format!("{} - {}", self.config.name, token_id); let links_and_data = Nft { - owner: msg_src, + owner: minter, name, description: token_description, metadata: vec![], @@ -103,16 +90,20 @@ impl NftContract { self.nonce = next_nft_nonce; self.tokens.insert(token_id, links_and_data.clone()); self.restriction_mint - .entry(msg_src) + .entry(minter) .and_modify(|ids| { *ids += 1; }) .or_insert(1); - Ok(MusicNftEvent::Minted { - token_id, - links_and_data, - }) + msg::send_with_gas( + minter, + Ok::(MusicNftEvent::Minted { token_id, links_and_data }), + 0, + 0, + ) + .expect("Error in sending message"); + Ok(MusicNftEvent::SuccessfullyMinted) } fn transfer_from( &mut self, @@ -120,26 +111,25 @@ impl NftContract { to: &ActorId, token_id: NftId, ) -> Result { + // checking for the possibility to transfer self.can_transfer(from, to, &token_id)?; let nft = self .tokens .get_mut(&token_id) - .expect("NonFungibleToken: token does not exist"); + .ok_or(MusicNftError::TokenDoesNotExist)?; + let tokens = self + .owners + .get_mut(from) + .ok_or(MusicNftError::OwnerDoesNotHaveNft)?; + tokens.retain(|&token| token != token_id); + + if tokens.is_empty() { + self.owners.remove(from); + } nft.owner = *to; - if let Some(tokens) = self.owners.get_mut(from) { - tokens.retain(|&token| token != token_id); - if tokens.is_empty() { - self.owners.remove(from); - } - } else { - return Err(MusicNftError( - "Fatal: owner does not contain nft id.".to_owned(), - )); - } - self.owners .entry(*to) .and_modify(|ids| { @@ -156,76 +146,68 @@ impl NftContract { }) } fn approve(&mut self, to: &ActorId, token_id: NftId) -> Result { + // checking for the possibility to give an approve self.can_approve(&token_id)?; self.token_approvals.insert(token_id, *to); - Ok(MusicNftEvent::Approved { - account: *to, - token_id, - }) + Ok(MusicNftEvent::Approved { to: *to, token_id }) } fn revoke_approve(&mut self, token_id: NftId) -> Result { - if let Some(nft_info) = self.tokens.get(&token_id) { - if nft_info.owner != msg::source() { - return Err(MusicNftError( - "Only nft owner can send this message".to_owned(), - )); - } - } else { - return Err(MusicNftError("This nft id hasn't come out yet".to_owned())); - } - let res = self.token_approvals.remove(&token_id); - if res.is_none() { - return Err(MusicNftError( - "No approve has been issued to this token".to_owned(), - )); + let nft_info = self + .tokens + .get(&token_id) + .ok_or(MusicNftError::TokenDoesNotExist)?; + if nft_info.owner != msg::source() { + return Err(MusicNftError::AccessDenied); } + let _ = self + .token_approvals + .remove(&token_id) + .ok_or(MusicNftError::NoApproval); Ok(MusicNftEvent::ApprovalRevoked { token_id }) } fn get_token_info(&self, token_id: NftId) -> Result { - let nft = self.tokens.get(&token_id); - let (token_owner, can_sell) = if let Some(nft) = nft { - let can_sell = if let Some(time) = self.config.sellable { - exec::block_timestamp() >= nft.mint_time + time - } else { - false - }; - (nft.owner, can_sell) - } else { - return Err(MusicNftError( - "NonFungibleToken: token does not exist".to_owned(), - )); - }; + let nft = self + .tokens + .get(&token_id) + .ok_or(MusicNftError::TokenDoesNotExist)?; + + let can_sell = self + .config + .sellable + .map(|time| exec::block_timestamp() >= nft.mint_time + time) + .unwrap_or_default(); + let approval = self.token_approvals.get(&token_id).copied(); Ok(MusicNftEvent::TokenInfoReceived { - token_owner, + token_owner: nft.owner, approval, sellable: can_sell, collection_owner: self.collection_owner, royalty: self.config.royalty, }) } - fn expand( - &mut self, - additional_links: Vec<(Links, ImageData)>, - ) -> Result { - self.check_collection_owner()?; - + fn get_payment_for_mint(&self) -> Result { + Ok(MusicNftEvent::PaymentForMintReceived { + payment_for_mint: self.config.payment_for_mint, + }) + } + fn expand(&mut self, additional_links: Vec<(Links, ImageData)>) -> Result { + let msg_src = msg::source(); + self.check_admin(msg_src)?; if additional_links .iter() .any(|(_, img_data)| img_data.limit_copies.map_or(false, |limit| limit == 0)) { - return Err(MusicNftError( - "Limit of copies value is equal to 0".to_owned(), - )); + return Err(MusicNftError::LimitIsZero); } self.links_and_data.extend(additional_links.clone()); - let mut number_tokens = sum_limit_copies(&self.links_and_data); + // The tokens that have already been minted should be added as well if let Some(all_copies) = number_tokens.as_mut() { *all_copies += self.tokens.len() as u64; } @@ -238,141 +220,134 @@ impl NftContract { } fn change_config(&mut self, config: Config) -> Result { - self.check_collection_owner()?; + let msg_src = msg::source(); + self.check_admin(msg_src)?; if !self.tokens.is_empty() { - return Err(MusicNftError( - "The collection configuration can no more be changed".to_owned(), - )); + return Err(MusicNftError::ConfigCannotBeChanged); } // made 10_000 so you can enter hundredths of a percent. if config.royalty > 10_000 { - return Err(MusicNftError( - "Royalty percent must be less than 100%".to_owned(), - )); + return Err(MusicNftError::WrongRoyalty); } - if config.transferable.is_none() && config.sellable.is_some() { - return Err(MusicNftError("Tokens must be transferable".to_owned())); + return Err(MusicNftError::NotTransferable); } if let Some(limit) = config.user_mint_limit { if limit == 0 { - return Err(MusicNftError( - "The mint limit must be greater than zero".to_owned(), - )); + return Err(MusicNftError::LimitIsZero); } } self.config = config.clone(); Ok(MusicNftEvent::ConfigChanged { config }) } - fn change_image( - &mut self, - token_id: NftId, - img_link: String, - ) -> Result { - if exec::program_id() != msg::source() { - return Err(MusicNftError( - "Only program can send this message".to_owned(), - )); + fn add_users_for_mint(&mut self, users: Vec) -> Result { + let msg_src = msg::source(); + self.check_admin(msg_src)?; + if let Some(allowed_users) = &mut self.permission_to_mint { + allowed_users.extend(users.clone()); + allowed_users.sort(); + allowed_users.dedup(); + } else if self.tokens.is_empty() { + let mut new_users = users.clone(); + new_users.push(msg_src); + self.permission_to_mint = Some(new_users); + } else { + return Err(MusicNftError::UserRestrictionCannotBeChanged); } - let nft = self - .tokens - .get_mut(&token_id) - .expect("Token does not exist"); - - nft.img_link = img_link.clone(); - msg::send( - nft.owner, - MusicNftEvent::ImageChanged { - token_id, - img_link: img_link.clone(), - }, - 0, - ) - .expect("Error in sending MusicNftEvent::ImageChanged"); - Ok(MusicNftEvent::ImageChanged { token_id, img_link }) + + Ok(MusicNftEvent::UsersForMintAdded { users }) } + fn delete_user_for_mint(&mut self, user: ActorId) -> Result { + let msg_src = msg::source(); + self.check_admin(msg_src)?; + let allowed_users = self + .permission_to_mint + .as_mut() + .ok_or(MusicNftError::NoListOfRestriction)?; + if let Some(pos) = allowed_users + .iter() + .position(|allowed_user| *allowed_user == user) + { + allowed_users.remove(pos); + } else { + return Err(MusicNftError::ThereIsNoSuchUser); + } - fn add_metadata( - &mut self, - token_id: NftId, - metadata: String, - ) -> Result { - if exec::program_id() != msg::source() { - return Err(MusicNftError( - "Only program can send this message".to_owned(), - )); + Ok(MusicNftEvent::UserForMintDeleted { user }) + } + + fn lift_restrictions_mint(&mut self) -> Result { + let msg_src = msg::source(); + self.check_admin(msg_src)?; + + if !self.tokens.is_empty() { + return Err(MusicNftError::UserRestrictionCannotBeChanged); } - let nft = self - .tokens - .get_mut(&token_id) - .expect("Token does not exist"); - - nft.metadata.push(metadata.clone()); - msg::send( - nft.owner, - MusicNftEvent::MetadataAdded { - token_id, - metadata: metadata.clone(), - }, - 0, - ) - .expect("Error in sending MusicNftEvent::MetadataAdded"); - Ok(MusicNftEvent::MetadataAdded { token_id, metadata }) + self.permission_to_mint = None; + + Ok(MusicNftEvent::LiftRestrictionMint) + } + + fn add_admin(&mut self, admin: ActorId) -> Result { + let msg_src = msg::source(); + self.check_admin(msg_src)?; + self.admins.insert(admin); + Ok(MusicNftEvent::AdminAdded { admin}) + } + + fn remove_admin(&mut self, admin: ActorId) -> Result { + let msg_src = msg::source(); + self.check_admin(msg_src)?; + if self.admins.len() == 1 { + return Err(MusicNftError::OnlyOneAdminLeft); + } + self.admins.remove(&admin); + Ok(MusicNftEvent::AdminRemoved { admin}) } fn can_delete(&self) -> Result { Ok(MusicNftEvent::CanDelete(self.tokens.is_empty())) } - fn check_collection_owner(&self) -> Result<(), MusicNftError> { - if self.collection_owner != msg::source() { - return Err(MusicNftError( - "Only collection owner can send this message".to_owned(), - )); + fn check_admin(&self, msg_src: ActorId) -> Result<(), MusicNftError> { + if !self.admins.contains(&msg_src) { + return Err(MusicNftError::AccessDenied); } Ok(()) } fn check_available_amount_of_tokens(&self) -> Result<(), MusicNftError> { if self.links_and_data.is_empty() { - return Err(MusicNftError("All tokens are minted.".to_owned())); + return Err(MusicNftError::AllTokensMinted); } Ok(()) } - fn check_mint_limit(&self, user: &ActorId) -> Result<(), MusicNftError> { + fn check_mint(&self, user: &ActorId) -> Result<(), MusicNftError> { + if let Some(permission_to_mint) = &self.permission_to_mint { + if !permission_to_mint.contains(user) { + return Err(MusicNftError::AccessDenied); + } + } + if let Some(limit) = self.config.user_mint_limit { if let Some(number_tokens) = self.restriction_mint.get(user) { if number_tokens >= &limit && self.collection_owner != *user { - return Err(MusicNftError("You've exhausted your limit.".to_owned())); + return Err(MusicNftError::ExhaustedLimit); } } } Ok(()) } - fn can_approve(&self, token_id: &NftId) -> Result<(), MusicNftError> { - if self.token_approvals.contains_key(token_id) { - return Err(MusicNftError("Approve has already been issued".to_owned())); - } - if let Some(nft_info) = self.tokens.get(token_id) { - if nft_info.owner != msg::source() { - return Err(MusicNftError( - "Only nft owner can send this message".to_owned(), - )); - } - } else { - return Err(MusicNftError("This nft id hasn't come out yet".to_owned())); - } - - Ok(()) - } - fn payment_for_mint(&self) -> Result<(), MusicNftError> { + + // Checking for sufficient mint value and sending the value to the creator + fn payment_for_mint(&self, msg_value: u128) -> Result<(), MusicNftError> { if self.config.payment_for_mint != 0 { - if msg::value() != self.config.payment_for_mint { - return Err(MusicNftError("Incorrectly entered mint fee.".to_owned())); + if msg_value != self.config.payment_for_mint { + return Err(MusicNftError::WrongValue); } // use send_with_gas to transfer the value directly to the balance, not to the mailbox. msg::send_with_gas(self.collection_owner, "", 0, self.config.payment_for_mint) @@ -381,54 +356,57 @@ impl NftContract { Ok(()) } + + // verification of approval + fn can_approve(&self, token_id: &NftId) -> Result<(), MusicNftError> { + if self.token_approvals.contains_key(token_id) { + return Err(MusicNftError::ThereIsApproval); + } + let nft_info = self + .tokens + .get(token_id) + .ok_or(MusicNftError::TokenDoesNotExist)?; + if nft_info.owner != msg::source() { + return Err(MusicNftError::AccessDenied); + } + Ok(()) + } + // approval check fn check_approve(&self, user: &ActorId, token_id: &NftId) -> Result<(), MusicNftError> { - if let Some(approved_account) = self.token_approvals.get(token_id) { - if approved_account != user { - return Err(MusicNftError( - "Caller is not approved to perform transfer.".to_owned(), - )); - } - } else { - return Err(MusicNftError("Target token_approvals is empty.".to_owned())); + let approved_account = self + .token_approvals + .get(token_id) + .ok_or(MusicNftError::AccessDenied)?; + if approved_account != user { + return Err(MusicNftError::AccessDenied); } + Ok(()) } - fn can_transfer( - &self, - from: &ActorId, - to: &ActorId, - token_id: &NftId, - ) -> Result<(), MusicNftError> { - if let Some(nft) = self.tokens.get(token_id) { - let owner = nft.owner; - if owner != *from { - return Err(MusicNftError("NonFungibleToken: access denied".to_owned())); - } - let msg_src = msg::source(); - if owner != msg_src { - self.check_approve(&msg_src, token_id)?; - } - if let Some(time) = self.config.transferable { - if exec::block_timestamp() < nft.mint_time + time { - return Err(MusicNftError( - "NonFungibleToken: transfer will be available after the deadline" - .to_owned(), - )); - } - } else { - return Err(MusicNftError( - "NonFungibleToken: token is not transferable".to_owned(), - )); - } - } else { - return Err(MusicNftError( - "NonFungibleToken: token does not exist".to_owned(), - )); + // doing all the checks to verify that the transfer can be made + fn can_transfer(&self, from: &ActorId, to: &ActorId, token_id: &NftId) -> Result<(), MusicNftError> { + let nft = self + .tokens + .get(token_id) + .ok_or(MusicNftError::TokenDoesNotExist)?; + + let owner = nft.owner; + if owner != *from { + return Err(MusicNftError::AccessDenied); + } + let msg_src = msg::source(); + // if the owner of the token does not match the sender of the message, then check the approval + if owner != msg_src { + self.check_approve(&msg_src, token_id)?; + } + let time = self.config.transferable.ok_or(MusicNftError::NotTransferable)?; + if exec::block_timestamp() < nft.mint_time + time { + return Err(MusicNftError::NotTransferable); } if from == to { - return Err(MusicNftError("Self transfer is not allowed.".to_owned())); + return Err(MusicNftError::AccessDenied); } Ok(()) } @@ -440,9 +418,19 @@ extern "C" fn init() { collection_owner, config, links_and_data, + mut permission_to_mint } = msg::load().expect("Unable to decode `MusicNftInit`."); - debug!("INIT NFT"); + let msg_value = msg::value(); + let picture_fee = FEE_PER_UPLOADED_FILE * links_and_data.len() as u128; + + let existential_deposit = exec::env_vars().existential_deposit; + + if picture_fee < existential_deposit && msg_value != existential_deposit + || picture_fee >= existential_deposit && msg_value != picture_fee + { + panic!("Wrong value for picture fee"); + } assert!( config .user_mint_limit @@ -450,27 +438,30 @@ extern "C" fn init() { .unwrap_or(true), "The mint limit must be greater than zero" ); - if config.payment_for_mint > 0 && config.payment_for_mint < EXISTENTIAL_DEPOSIT { + if config.payment_for_mint > 0 && config.payment_for_mint < existential_deposit { panic!( "{}", format!( "The payment for mint must be greater than existential deposit ({})", - EXISTENTIAL_DEPOSIT + existential_deposit ) ); } - // made 10_000 so you can enter hundredths of a percent. - if config.royalty > 10_000 { - panic!("Royalty percent must be less than 100%"); + // can't take more than MAX_CREATOR_ROYALTY + if config.royalty > MAX_CREATOR_ROYALTY { + panic!("Royalty percent must be less"); + } + // can't be made sellable but not transferable. + if config.transferable.is_none() && config.sellable.is_some() { + panic!("Tokens must be transferable"); } + // at least one image in the collection is a must have. assert!( !links_and_data.is_empty(), "There must be at least one link to create a collection" ); - if config.transferable.is_none() && config.sellable.is_some() { - panic!("Tokens must be transferable"); - } + // сan't have zero copies of the nft. if links_and_data .iter() .any(|(_, img_data)| img_data.limit_copies.map_or(false, |limit| limit == 0)) @@ -478,7 +469,13 @@ extern "C" fn init() { panic!("Limit of copies value is equal to 0"); } let total_number_of_tokens = sum_limit_copies(&links_and_data); - + if let Some(users) = permission_to_mint.as_mut() { + if !users.contains(&collection_owner) { + users.push(collection_owner); + } + } + let mut admins = HashSet::new(); + admins.insert(collection_owner); unsafe { NFT_CONTRACT = Some(NftContract { tokens: HashMap::new(), @@ -490,6 +487,9 @@ extern "C" fn init() { links_and_data, collection_owner, total_number_of_tokens, + permission_to_mint: permission_to_mint.clone(), + marketplace_address: msg::source(), + admins, }) }; @@ -498,6 +498,7 @@ extern "C" fn init() { Ok::(MusicNftEvent::Initialized { config: config.clone(), total_number_of_tokens, + permission_to_mint: permission_to_mint.clone(), }), 0, ) @@ -507,6 +508,7 @@ extern "C" fn init() { MusicNftEvent::Initialized { config: config.clone(), total_number_of_tokens, + permission_to_mint, }, 0, ) @@ -523,7 +525,20 @@ extern "C" fn handle() { }; let result = match action { - MusicNftAction::Mint => nft_contract.mint(), + MusicNftAction::Mint { minter } => { + let msg_source = msg::source(); + let msg_value = msg::value(); + let result = if msg_source != nft_contract.marketplace_address { + Err(MusicNftError::AccessDenied) + } else { + nft_contract.mint(minter, msg_value) + }; + + if result.is_err() { + msg::send_with_gas(msg_source, "", 0, msg_value).expect("Error in sending value"); + } + result + } MusicNftAction::TransferFrom { from, to, token_id } => { nft_contract.transfer_from(&from, &to, token_id) } @@ -534,14 +549,14 @@ extern "C" fn handle() { MusicNftAction::RevokeApproval { token_id } => nft_contract.revoke_approve(token_id), MusicNftAction::Expand { additional_links } => nft_contract.expand(additional_links), MusicNftAction::ChangeConfig { config } => nft_contract.change_config(config), - MusicNftAction::ChangeImg { token_id, img_link } => { - nft_contract.change_image(token_id, img_link) - } - MusicNftAction::AddMetadata { token_id, metadata } => { - nft_contract.add_metadata(token_id, metadata) - } MusicNftAction::GetTokenInfo { token_id } => nft_contract.get_token_info(token_id), + MusicNftAction::GetPaymentForMint => nft_contract.get_payment_for_mint(), MusicNftAction::CanDelete => nft_contract.can_delete(), + MusicNftAction::AddUsersForMint { users } => nft_contract.add_users_for_mint(users), + MusicNftAction::DeleteUserForMint { user } => nft_contract.delete_user_for_mint(user), + MusicNftAction::LiftRestrictionMint => nft_contract.lift_restrictions_mint(), + MusicNftAction::AddAdmin { admin } => nft_contract.add_admin(admin), + MusicNftAction::RemoveAdmin { admin } => nft_contract.remove_admin(admin), }; msg::reply(result, 0) @@ -579,20 +594,27 @@ impl From for NftState { links_and_data, collection_owner, total_number_of_tokens, + permission_to_mint, + marketplace_address, + admins, .. } = value; let tokens = tokens - .iter() - .map(|(nft_id, nft)| (*nft_id, nft.clone())) + .into_iter() + .map(|(nft_id, nft)| (nft_id, nft)) .collect(); let owners = owners - .iter() - .map(|(actor_id, token_set)| (*actor_id, token_set.iter().copied().collect())) + .into_iter() + .map(|(actor_id, token_set)| (actor_id, token_set.into_iter().collect())) .collect(); let token_approvals = token_approvals - .iter() - .map(|(nft_id, actor_id)| (*nft_id, *actor_id)) + .into_iter() + .map(|(nft_id, actor_id)| (nft_id, actor_id)) + .collect(); + + let admins = admins + .into_iter() .collect(); Self { @@ -604,6 +626,9 @@ impl From for NftState { links_and_data, collection_owner, total_number_of_tokens, + permission_to_mint, + marketplace_address, + admins } } } diff --git a/nft/io/src/lib.rs b/nft/io/src/lib.rs index ec7d131..699a5d5 100644 --- a/nft/io/src/lib.rs +++ b/nft/io/src/lib.rs @@ -193,6 +193,7 @@ pub struct NftState { pub total_number_of_tokens: Option, pub permission_to_mint: Option>, pub marketplace_address: ActorId, + pub admins: Vec, } #[derive(Debug, Clone, Encode, Decode, TypeInfo)] diff --git a/nft/src/lib.rs b/nft/src/lib.rs index 7fd3d0b..73616c9 100644 --- a/nft/src/lib.rs +++ b/nft/src/lib.rs @@ -589,6 +589,7 @@ impl From for NftState { total_number_of_tokens, permission_to_mint, marketplace_address, + admins, .. } = value; @@ -602,6 +603,9 @@ impl From for NftState { let token_approvals = token_approvals .into_iter() .collect(); + let admins = admins + .into_iter() + .collect(); Self { tokens, @@ -613,7 +617,8 @@ impl From for NftState { collection_owner, total_number_of_tokens, permission_to_mint, - marketplace_address + marketplace_address, + admins, } } } diff --git a/src/auction.rs b/src/auction.rs index b4b7d6c..59de268 100644 --- a/src/auction.rs +++ b/src/auction.rs @@ -12,6 +12,8 @@ impl NftMarketplace { min_price: u128, duration: u32, ) -> Result { + let msg_src = msg::source(); + self.check_allow_message(&msg_src)?; if !self.collection_to_owner.contains_key(&collection_address) { return Err(NftMarketplaceError::WrongCollectionAddress); } @@ -28,7 +30,6 @@ impl NftMarketplace { // check token info let address_marketplace = exec::program_id(); - let msg_src = msg::source(); let (collection_owner, royalty) = check_token_info( &collection_address, token_id, @@ -91,10 +92,11 @@ impl NftMarketplace { &mut self, collection_address: ActorId, token_id: u64, + msg_src: ActorId, + msg_value: u128, ) -> Result { - let msg_value = msg::value(); - let msg_src = msg::source(); - let auction = self.check_auction(&msg_src, &collection_address, &token_id, &msg_value)?; + self.check_allow_message(&msg_src)?; + let auction = self.check_auction(&collection_address, &token_id, &msg_value)?; if auction.current_winner != ActorId::zero() { // use send_with_gas with gas_limit = 0 to transfer the value directly to the balance, not to the mailbox. @@ -184,12 +186,15 @@ impl NftMarketplace { collection_address: ActorId, token_id: u64, ) -> Result { + let msg_src = msg::source(); + self.check_allow_message(&msg_src)?; + let auction = self .auctions .get(&(collection_address, token_id)) .ok_or(NftMarketplaceError::ThereIsNoSuchAuction)?; - if auction.owner != msg::source() { + if auction.owner != msg_src { return Err(NftMarketplaceError::AccessDenied); } transfer_token( @@ -218,7 +223,6 @@ impl NftMarketplace { fn check_auction( &mut self, - msg_src: &ActorId, collection_address: &ActorId, token_id: &u64, bid: &u128, @@ -226,18 +230,15 @@ impl NftMarketplace { let auction = if let Some(auction) = self.auctions.get_mut(&(*collection_address, *token_id)) { if auction.ended_at < exec::block_timestamp() { - msg::send_with_gas(*msg_src, NftMarketplaceEvent::ValueSent, 0, *bid).expect("Error in sending value"); return Err(NftMarketplaceError::AuctionClosed); } // if the first bid, it may be equal to the `current_price` (initial bid) if auction.current_winner != ActorId::zero() && *bid <= auction.current_price || auction.current_winner == ActorId::zero() && *bid < auction.current_price { - msg::send_with_gas(*msg_src, NftMarketplaceEvent::ValueSent, 0, *bid).expect("Error in sending value"); return Err(NftMarketplaceError::LessOrEqualThanBid); } auction } else { - msg::send_with_gas(*msg_src, NftMarketplaceEvent::ValueSent, 0, *bid).expect("Error in sending value"); return Err(NftMarketplaceError::ThereIsNoSuchAuction); }; Ok(auction) diff --git a/src/lib.rs b/src/lib.rs index f3060df..9398825 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,8 @@ pub struct NftMarketplace { pub offers: HashMap, pub config: Config, pub minimum_value_for_trade: u128, + pub allow_message: bool, + pub allow_create_collection: bool, } static mut NFT_MARKETPLACE: Option = None; @@ -96,18 +98,32 @@ async fn main() { .as_mut() .expect("`Nft Marketplace` is not initialized.") }; + let result = match action { NftMarketplaceAction::AddNewCollection { code_id, meta_link, type_name, type_description, - } => nft_marketplace.add_new_collection(code_id, meta_link, type_name, type_description), + allow_create, + } => nft_marketplace.add_new_collection(code_id, meta_link, type_name, type_description, allow_create), NftMarketplaceAction::CreateCollection { type_name, payload } => { - nft_marketplace.create_collection(type_name, payload).await + let msg_source = msg::source(); + let msg_value = msg::value(); + let reply = nft_marketplace.create_collection(type_name, payload, msg_source, msg_value).await; + if reply.is_err() { + msg::send_with_gas(msg_source, NftMarketplaceEvent::ValueSent, 0, msg_value).expect("Error in sending value"); + } + reply } NftMarketplaceAction::Mint { collection_address } => { - nft_marketplace.mint(collection_address).await + let msg_source = msg::source(); + let msg_value = msg::value(); + let reply = nft_marketplace.mint(collection_address, msg_source, msg_value).await; + if reply.is_err() { + msg::send_with_gas(msg_source, NftMarketplaceEvent::ValueSent, 0, msg_value).expect("Error in sending value"); + } + reply } NftMarketplaceAction::SaleNft { collection_address, @@ -153,7 +169,16 @@ async fn main() { NftMarketplaceAction::AddBid { collection_address, token_id, - } => nft_marketplace.add_bid(collection_address, token_id), + } => { + let msg_source = msg::source(); + let msg_value = msg::value(); + let reply = nft_marketplace.add_bid(collection_address, token_id, msg_source, msg_value); + if reply.is_err() { + msg::send_with_gas(msg_source, NftMarketplaceEvent::ValueSent, 0, msg_value).expect("Error in sending value"); + } + reply + + } NftMarketplaceAction::CloseAuction { collection_address, token_id, @@ -224,6 +249,9 @@ async fn main() { max_creator_royalty, max_number_of_images ), + NftMarketplaceAction::AllowMessage(allow) => nft_marketplace.allow_message(allow), + NftMarketplaceAction::AllowCreateCollection(allow) => nft_marketplace.allow_create_collection(allow), + NftMarketplaceAction::AddExternalCollection { collection_address, type_name } => nft_marketplace.add_external_collection(collection_address, type_name), }; msg::reply(result, 0).expect( @@ -238,14 +266,16 @@ impl NftMarketplace { meta_link: String, type_name: String, type_description: String, + allow_create: bool, ) -> Result { let msg_src = msg::source(); - self.check_admin(msg_src)?; + self.check_admin(&msg_src)?; let collection_info = TypeCollectionInfo { code_id, meta_link: meta_link.clone(), type_description: type_description.clone(), + allow_create, }; self.type_collections .insert(type_name.clone(), collection_info.clone()); @@ -262,12 +292,18 @@ impl NftMarketplace { &mut self, type_name: String, payload: Vec, + msg_src: ActorId, + msg_value: u128, ) -> Result { - let msg_src = msg::source(); - let msg_value = msg::value(); + self.check_time_creation(&msg_src)?; + self.check_allow_create_collection(&msg_src)?; + self.check_allow_message(&msg_src)?; let collection_info = self.get_collection_info(&type_name)?; + if !collection_info.allow_create { + return Err(NftMarketplaceError::AccessDenied); + } let address = match ProgramGenerator::create_program_bytes_with_gas_for_reply( collection_info.code_id, @@ -279,12 +315,10 @@ impl NftMarketplace { Ok(future) => match future.await { Ok((address, _)) => address, Err(_) => { - msg::send_with_gas(msg_src, NftMarketplaceEvent::ValueSent, 0, msg_value).expect("Error in sending value"); return Err(NftMarketplaceError::CreationError); } }, Err(_) => { - msg::send_with_gas(msg_src, NftMarketplaceEvent::ValueSent, 0, msg_value).expect("Error in sending value"); return Err(NftMarketplaceError::CreationError); } }; @@ -305,22 +339,19 @@ impl NftMarketplace { pub async fn mint( &mut self, collection_address: ActorId, + msg_src: ActorId, + msg_value: u128 ) -> Result { - let msg_src = msg::source(); - let msg_value = msg::value(); - - let reply = mint( + self.check_allow_message(&msg_src)?; + mint( collection_address, msg_src, + msg_value, self.config.gas_for_mint, self.config.gas_for_get_info, self.config.royalty_to_marketplace_for_mint, ) - .await; - if reply.is_err() { - msg::send_with_gas(msg_src, NftMarketplaceEvent::ValueSent, 0, msg_value).expect("Error in sending value"); - } - reply + .await } pub async fn delete_collection( @@ -369,7 +400,7 @@ impl NftMarketplace { users: Vec, ) -> Result { let msg_src = msg::source(); - self.check_admin(msg_src)?; + self.check_admin(&msg_src)?; self.admins.extend(users.clone()); Ok(NftMarketplaceEvent::AdminsAdded { users }) } @@ -378,7 +409,7 @@ impl NftMarketplace { user: ActorId, ) -> Result { let msg_src = msg::source(); - self.check_admin(msg_src)?; + self.check_admin(&msg_src)?; self.admins.retain(|&admin| admin != user); Ok(NftMarketplaceEvent::AdminDeleted { user }) } @@ -399,7 +430,7 @@ impl NftMarketplace { max_number_of_images: Option, ) -> Result { let msg_src = msg::source(); - self.check_admin(msg_src)?; + self.check_admin(&msg_src)?; if let Some(gas) = gas_for_creation { self.config.gas_for_creation = gas; } @@ -462,10 +493,47 @@ impl NftMarketplace { max_number_of_images }) } + pub fn allow_message( + &mut self, + allow: bool, + ) -> Result { + let msg_src = msg::source(); + self.check_admin(&msg_src)?; + self.allow_message = allow; + Ok(NftMarketplaceEvent::AllowMessageChanged) + + } + pub fn allow_create_collection( + &mut self, + allow: bool, + ) -> Result { + let msg_src = msg::source(); + self.check_admin(&msg_src)?; + self.allow_create_collection = allow; + Ok(NftMarketplaceEvent::AllowCreateCollectionChanged) + + } + + pub fn add_external_collection( + &mut self, + collection_address: ActorId, + type_name: String, + ) -> Result { + let msg_src = msg::source(); + self.check_admin(&msg_src)?; + if !self.type_collections.contains_key(&type_name){ + return Err(NftMarketplaceError::WrongCollectionName); + } + self.collection_to_owner.insert(collection_address, (type_name.clone(), msg_src)); + Ok(NftMarketplaceEvent::CollectionCreated { + type_name, + collection_address, + }) + } pub fn balance_out(&mut self) -> Result { let msg_src = msg::source(); - self.check_admin(msg_src)?; + self.check_admin(&msg_src)?; let balance = exec::value_available(); let existential_deposit = exec::env_vars().existential_deposit; if balance < 2*existential_deposit { @@ -487,8 +555,21 @@ impl NftMarketplace { Ok(()) } - fn check_admin(&self, msg_src: ActorId) -> Result<(), NftMarketplaceError> { - if !self.admins.contains(&msg_src) { + fn check_admin(&self, msg_src: &ActorId) -> Result<(), NftMarketplaceError> { + if !self.admins.contains(msg_src) { + return Err(NftMarketplaceError::AccessDenied); + } + Ok(()) + } + fn check_allow_message(&self, msg_src: &ActorId) -> Result<(), NftMarketplaceError> { + if !self.allow_message && !self.admins.contains(msg_src) { + return Err(NftMarketplaceError::AccessDenied); + } + + Ok(()) + } + fn check_allow_create_collection(&self, msg_src: &ActorId) -> Result<(), NftMarketplaceError> { + if !self.allow_create_collection && !self.admins.contains(msg_src) { return Err(NftMarketplaceError::AccessDenied); } Ok(()) @@ -574,6 +655,8 @@ impl From for State { offers, config, minimum_value_for_trade, + allow_message, + allow_create_collection } = value; let collection_to_owner = collection_to_owner @@ -607,6 +690,8 @@ impl From for State { offers, config, minimum_value_for_trade, + allow_message, + allow_create_collection } } } diff --git a/src/nft_messages.rs b/src/nft_messages.rs index db312f1..2d97567 100644 --- a/src/nft_messages.rs +++ b/src/nft_messages.rs @@ -1,17 +1,16 @@ -use gstd::{msg, prelude::*, ActorId, exec}; +use gstd::{msg, prelude::*, ActorId}; use nft_marketplace_io::*; pub async fn mint( collection_address: ActorId, minter: ActorId, + msg_value: u128, gas_for_mint: u64, gas_for_get_info: u64, royalty_to_marketplace: u16, ) -> Result { - let msg_value = msg::value(); let get_payment_for_mint_payload = NftAction::GetPaymentForMint; - let reply = msg::send_with_gas_for_reply_as::>( collection_address, get_payment_for_mint_payload, diff --git a/src/offer.rs b/src/offer.rs index 3e84bf3..2e9dadf 100644 --- a/src/offer.rs +++ b/src/offer.rs @@ -12,6 +12,7 @@ impl NftMarketplace { msg_src: ActorId, msg_value: u128, ) -> Result { + self.check_allow_message(&msg_src)?; if msg_value < self.minimum_value_for_trade { return Err(NftMarketplaceError::LessThanMinimumValueForTrade); } @@ -76,10 +77,12 @@ impl NftMarketplace { collection_address: ActorId, token_id: u64, ) -> Result { + let msg_src = msg::source(); + self.check_allow_message(&msg_src)?; let offer = Offer { collection_address, token_id, - creator: msg::source(), + creator: msg_src, }; if let Some((offer, price)) = self.offers.get_key_value(&offer) { @@ -100,6 +103,8 @@ impl NftMarketplace { &mut self, offer: Offer, ) -> Result { + let msg_src = msg::source(); + self.check_allow_message(&msg_src)?; if self .sales .contains_key(&(offer.collection_address, offer.token_id)) @@ -118,7 +123,6 @@ impl NftMarketplace { // check token info let address_marketplace = exec::program_id(); - let msg_src = msg::source(); let (collection_owner, royalty) = check_token_info( &offer.collection_address, offer.token_id, diff --git a/src/sale.rs b/src/sale.rs index ce4878c..25e9721 100644 --- a/src/sale.rs +++ b/src/sale.rs @@ -11,6 +11,8 @@ impl NftMarketplace { token_id: u64, price: u128, ) -> Result { + let msg_src = msg::source(); + self.check_allow_message(&msg_src)?; // check that this collection already exists in the marketplace if !self.collection_to_owner.contains_key(&collection_address) { return Err(NftMarketplaceError::WrongCollectionAddress); @@ -30,7 +32,6 @@ impl NftMarketplace { // send a message to the nft contract to find out information about the token let address_marketplace = exec::program_id(); - let msg_src = msg::source(); let (collection_owner, royalty) = check_token_info( &collection_address, token_id, @@ -81,9 +82,11 @@ impl NftMarketplace { collection_address: ActorId, token_id: u64, ) -> Result { + let msg_src = msg::source(); + self.check_allow_message(&msg_src)?; // check that such a sale exists and that the sender of the message is the owner of the sale/nft if let Some(nft_info) = self.sales.get(&(collection_address, token_id)) { - if nft_info.token_owner == msg::source() { + if nft_info.token_owner == msg_src { // return the token to its owner if let NftEvent::Transferred { owner: _, @@ -122,6 +125,7 @@ impl NftMarketplace { buyer: ActorId, msg_value: u128, ) -> Result { + self.check_allow_message(&buyer)?; self.check_sale(&collection_address, &token_id, msg_value)?; // transfer the token to the buyer transfer_token( diff --git a/tests/test_music_nft.rs b/tests/test_music_nft.rs index 811d9f6..443bf5d 100644 --- a/tests/test_music_nft.rs +++ b/tests/test_music_nft.rs @@ -1,966 +1,745 @@ -// use crate::utils::*; -// use utils::prelude::*; -// mod utils; -// use gtest::Program; -// use music_nft_io::{ -// Action, AdditionalLinks, Config, ImageData, Links, ListenCapability, MusicNftAction, -// MusicNftError, MusicNftEvent, MusicNftInit, NftState, StateQuery as StateQueryNft, -// StateReply as StateReplyNft, -// }; -// use nft_marketplace_io::*; - -// const USERS: &[u64] = &[5, 6, 7, 8]; - -// #[test] -// fn successful_basics() { -// let sys = utils::initialize_system(); -// init_marketplace(&sys); -// let marketplace = sys.get_program(1); -// let nft_collection_code_id = -// sys.submit_code("target/wasm32-unknown-unknown/debug/music_nft.opt.wasm"); - -// let state_reply = marketplace -// .read_state(StateQuery::CollectionsInfo) -// .expect("Unexpected invalid state."); -// if let StateReply::CollectionsInfo(state) = state_reply { -// assert!(state.is_empty(), "Collection info should be empty"); -// println!("Collection info: {:?}", state); -// } -// // Successful addition of a new collection -// let name_simple_nft = "Simple NFT".to_string(); -// let res = add_new_collection( -// &marketplace, -// ADMINS[0], -// nft_collection_code_id.into_bytes().into(), -// name_simple_nft.clone(), -// ); -// assert!(!res.main_failed()); -// let state_reply = marketplace -// .read_state(StateQuery::CollectionsInfo) -// .expect("Unexpected invalid state."); -// if let StateReply::CollectionsInfo(state) = state_reply { -// assert!(!state.is_empty(), "Collection info shouldn't be empty"); -// println!("Collection info: {:?}", state); -// } -// // Successful creation of a new collection -// let img_data = ImageData { -// limit_copies: Some(1), -// description: None, -// auto_changing_rules: None, -// }; -// let links_and_data: Vec<(Links, ImageData)> = (0..10) -// .map(|i| { -// ( -// Links { -// img_link: None, -// music_link: format!("Img-{}", i), -// }, -// img_data.clone(), -// ) -// }) -// .collect(); - -// let additional_links = Some(AdditionalLinks { -// external_url: Some("External link".to_string()), -// telegram: None, -// xcom: None, -// medium: None, -// discord: None, -// }); - -// // Successful creation of a new collection -// let init_nft_payload = MusicNftInit { -// collection_owner: USERS[0].into(), -// config: Config { -// name: "User Collection".to_string(), -// description: "User Collection".to_string(), -// collection_banner: "Collection banner".to_string(), -// collection_logo: "Collection logo".to_string(), -// collection_tags: vec!["tag1".to_string()], -// additional_links, -// royalty: 0, -// user_mint_limit: 3.into(), -// listening_capabilities: ListenCapability::Demo, -// payment_for_mint: 0, -// transferable: Some(0), -// sellable: Some(0), -// }, -// links_and_data, -// }; - -// let res = create_collection( -// &marketplace, -// USERS[0], -// name_simple_nft.clone(), -// init_nft_payload.encode(), -// ); -// assert!(!res.main_failed()); -// let result = &res.decoded_log::>(); -// println!("RES: {:?}", result); -// let state_reply = marketplace -// .read_state(StateQuery::AllCollections) -// .expect("Unexpected invalid state."); -// let address_nft = if let StateReply::AllCollections(state) = state_reply { -// assert!(!state.is_empty(), "Collections shouldn't be empty"); -// println!("Collections: {:?}", state); -// state[0].0 -// } else { -// assert!(false, "Unexpected StateReply variant"); -// 0.into() -// }; - -// let address_nft: [u8; 32] = address_nft.into(); -// let nft_collection = sys.get_program(address_nft); -// let state_reply = nft_collection -// .read_state(StateQueryNft::All) -// .expect("Unexpected invalid state."); -// if let StateReplyNft::All(state) = state_reply { -// assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); -// println!("Collection NFT info: {:?}", state); -// } - -// // Successful change config in the new collection -// let config = Config { -// name: "My Collection".to_string(), -// description: "My Collection".to_string(), -// collection_banner: "Collection banner".to_string(), -// collection_logo: "Collection logo".to_string(), -// collection_tags: vec!["tag1".to_string()], -// additional_links: None, -// royalty: 0, -// user_mint_limit: 3.into(), -// listening_capabilities: ListenCapability::Demo, -// payment_for_mint: 0, -// transferable: Some(0), -// sellable: Some(0), -// }; -// let res = nft_collection.send(USERS[0], MusicNftAction::ChangeConfig { config }); -// assert!(!res.main_failed()); -// let state_reply = nft_collection -// .read_state(StateQueryNft::All) -// .expect("Unexpected invalid state."); -// if let StateReplyNft::All(state) = state_reply { -// assert_eq!(state.config.name, "My Collection".to_string()); -// assert_eq!(state.config.description, "My Collection".to_string()); -// } - -// // Successful mint NFT in the new collection -// let res = nft_collection.send(USERS[1], MusicNftAction::Mint); -// assert!(!res.main_failed()); - -// let state_reply = nft_collection -// .read_state(StateQueryNft::All) -// .expect("Unexpected invalid state."); -// if let StateReplyNft::All(state) = state_reply { -// println!("Collection NFT info: {:?}", state); -// assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); -// let (owner, token_id) = state.owners.get(0).expect("Can't be None"); -// assert_eq!(*owner, USERS[1].into(), "Wrong owner"); -// assert_eq!(*token_id, vec![0], "Wrong token id"); -// } - -// // Successful transfer NFT in the collection -// let res = nft_collection.send( -// USERS[1], -// MusicNftAction::Transfer { -// to: USERS[2].into(), -// token_id: 0, -// }, -// ); -// assert!(!res.main_failed()); - -// let state_reply = nft_collection -// .read_state(StateQueryNft::All) -// .expect("Unexpected invalid state."); -// if let StateReplyNft::All(state) = state_reply { -// println!("!!!!!!!!!! STATE: {:?}", state); -// let (owner, token_id) = state.owners.get(0).expect("Can't be None"); -// assert_eq!(*owner, USERS[2].into(), "Wrong owner"); -// assert_eq!(*token_id, vec![0], "Wrong token id"); -// } - -// // Successful approve NFT in the collection (USERS[2] -Approve-> USERS[1]) -// let res = nft_collection.send( -// USERS[2], -// MusicNftAction::Approve { -// to: USERS[1].into(), -// token_id: 0, -// }, -// ); -// assert!(!res.main_failed()); - -// let state_reply = nft_collection -// .read_state(StateQueryNft::All) -// .expect("Unexpected invalid state."); -// if let StateReplyNft::All(state) = state_reply { -// let (token_id, approval) = state.token_approvals.get(0).expect("Can't be None"); -// assert_eq!(*token_id, 0, "Wrong owner"); -// assert_eq!(*approval, USERS[1].into(), "Wrong token id"); -// } -// // Check approve (USERS[1] -Transfer-> USERS[3]) -// let res = nft_collection.send( -// USERS[1], -// MusicNftAction::TransferFrom { -// from: USERS[2].into(), -// to: USERS[3].into(), -// token_id: 0, -// }, -// ); -// assert!(!res.main_failed()); - -// let state_reply = nft_collection -// .read_state(StateQueryNft::All) -// .expect("Unexpected invalid state."); -// if let StateReplyNft::All(state) = state_reply { -// println!("STATE OWNERS: {:?}", state.owners); -// let (owner, token_id) = state.owners.get(0).expect("Can't be None"); -// assert_eq!(*owner, USERS[3].into(), "Wrong owner"); -// assert_eq!(*token_id, vec![0], "Wrong token id"); -// } - -// // Check limit of mint = 3 -// let res = nft_collection.send(USERS[3], MusicNftAction::Mint); -// assert!(!res.main_failed()); -// let res = nft_collection.send(USERS[3], MusicNftAction::Mint); -// assert!(!res.main_failed()); -// let res = nft_collection.send(USERS[3], MusicNftAction::Mint); -// assert!(!res.main_failed()); -// let res = nft_collection.send(USERS[3], MusicNftAction::Mint); -// assert!(!res.main_failed()); -// // let res = res.decoded_log::>()[0]; -// //println!("RES {:?}", res); -// let message: Result = -// Err(MusicNftError("You've exhausted your limit.".to_owned())); -// assert!(res.contains(&(USERS[3], message.encode()))); - -// // Successful Expand NFT in the collection -// let img_data = ImageData { -// limit_copies: Some(1), -// description: None, -// auto_changing_rules: None, -// }; -// let res = nft_collection.send( -// USERS[0], -// MusicNftAction::Expand { -// additional_links: vec![ -// ( -// Links { -// img_link: None, -// music_link: "add_link_1".to_string(), -// }, -// img_data.clone(), -// ), -// ( -// Links { -// img_link: None, -// music_link: "add_link_2".to_string(), -// }, -// img_data.clone(), -// ), -// ], -// }, -// ); -// assert!(!res.main_failed()); -// let state_reply = nft_collection -// .read_state(StateQueryNft::All) -// .expect("Unexpected invalid state."); -// if let StateReplyNft::All(state) = state_reply { -// assert_eq!( -// state.links_and_data.len(), -// 8, -// "Wrong length of links_and_data" -// ); -// println!("STATE: {:?}", state); -// } -// } - -// #[test] -// fn failures() { -// let sys = utils::initialize_system(); -// init_marketplace(&sys); -// let marketplace = sys.get_program(1); -// let nft_collection_code_id = -// sys.submit_code("target/wasm32-unknown-unknown/debug/music_nft.opt.wasm"); - -// let name_simple_nft = "Simple NFT".to_string(); -// let res = add_new_collection( -// &marketplace, -// ADMINS[0], -// nft_collection_code_id.into_bytes().into(), -// name_simple_nft.clone(), -// ); -// assert!(!res.main_failed()); - -// // The mint limit must be greater than zero -// let img_data = ImageData { -// limit_copies: Some(1), -// description: None, -// auto_changing_rules: None, -// }; -// let links_and_data: Vec<(Links, ImageData)> = (0..10) -// .map(|i| { -// ( -// Links { -// img_link: None, -// music_link: format!("Img-{}", i), -// }, -// img_data.clone(), -// ) -// }) -// .collect(); - -// let additional_links = Some(AdditionalLinks { -// external_url: Some("External link".to_string()), -// telegram: None, -// xcom: None, -// medium: None, -// discord: None, -// }); - -// // Successful creation of a new collection -// let mut init_nft_payload = MusicNftInit { -// collection_owner: USERS[0].into(), -// config: Config { -// name: "User Collection".to_string(), -// description: "User Collection".to_string(), -// collection_banner: "Collection banner".to_string(), -// collection_logo: "Collection logo".to_string(), -// collection_tags: vec!["tag1".to_string()], -// additional_links, -// royalty: 0, -// user_mint_limit: 0.into(), -// listening_capabilities: ListenCapability::Demo, -// payment_for_mint: 0, -// transferable: Some(0), -// sellable: Some(0), -// }, -// links_and_data, -// }; - -// let res = create_collection( -// &marketplace, -// USERS[0], -// name_simple_nft.clone(), -// init_nft_payload.encode(), -// ); -// assert!(res.main_failed()); - -// // There must be at least one link to create a collection -// init_nft_payload.config.user_mint_limit = 4.into(); -// init_nft_payload.links_and_data = vec![]; -// let res = create_collection( -// &marketplace, -// USERS[0], -// name_simple_nft.clone(), -// init_nft_payload.encode(), -// ); -// assert!(res.main_failed()); - -// // Limit of copies value is equal to 0 -// let img_data = ImageData { -// limit_copies: Some(0), -// description: None, -// auto_changing_rules: None, -// }; -// init_nft_payload.links_and_data = vec![( -// Links { -// img_link: None, -// music_link: "add_link_1".to_string(), -// }, -// img_data.clone(), -// )]; - -// let res = create_collection( -// &marketplace, -// USERS[0], -// name_simple_nft.clone(), -// init_nft_payload.encode(), -// ); -// assert!(res.main_failed()); - -// let img_data = ImageData { -// limit_copies: Some(1), -// description: None, -// auto_changing_rules: None, -// }; -// let links_and_data: Vec<(Links, ImageData)> = (0..5) -// .map(|i| { -// ( -// Links { -// img_link: None, -// music_link: format!("Img-{}", i), -// }, -// img_data.clone(), -// ) -// }) -// .collect(); - -// init_nft_payload.links_and_data = links_and_data; -// let res = create_collection( -// &marketplace, -// USERS[0], -// name_simple_nft.clone(), -// init_nft_payload.encode(), -// ); -// assert!(!res.main_failed()); - -// let state_reply = marketplace -// .read_state(StateQuery::AllCollections) -// .expect("Unexpected invalid state."); -// let address_nft = if let StateReply::AllCollections(state) = state_reply { -// assert!(!state.is_empty(), "Collections shouldn't be empty"); -// println!("Collections: {:?}", state); -// state[0].0 -// } else { -// assert!(false, "Unexpected StateReply variant"); -// 0.into() -// }; - -// let address_nft: [u8; 32] = address_nft.into(); -// let nft_collection = sys.get_program(address_nft); - -// for _ in 0..4 { -// let res = nft_collection.send(USERS[1], MusicNftAction::Mint); -// assert!(!res.main_failed()); -// } -// let res = nft_collection.send(USERS[1], MusicNftAction::Mint); -// assert!(check_payload( -// 0, -// &res, -// "You've exhausted your limit.".to_string() -// )); - -// assert!(!res.main_failed()); - -// let res = nft_collection.send(USERS[2], MusicNftAction::Mint); -// assert!(!res.main_failed()); -// let res = nft_collection.send(USERS[2], MusicNftAction::Mint); -// assert!(check_payload(0, &res, "All tokens are minted.".to_string())); -// assert!(!res.main_failed()); - -// let config = Config { -// name: "User Collection".to_string(), -// description: "User Collection".to_string(), -// collection_banner: "Collection banner".to_string(), -// collection_logo: "Collection logo".to_string(), -// collection_tags: vec!["tag1".to_string()], -// additional_links: None, -// royalty: 0, -// user_mint_limit: 3.into(), -// listening_capabilities: ListenCapability::Demo, -// payment_for_mint: 0, -// transferable: Some(0), -// sellable: Some(0), -// }; -// let res = nft_collection.send(USERS[0], MusicNftAction::ChangeConfig { config }); -// assert!(check_payload( -// 0, -// &res, -// "The collection configuration can no more be changed".to_string() -// )); -// assert!(!res.main_failed()); - -// let state_reply = nft_collection -// .read_state(StateQueryNft::All) -// .expect("Unexpected invalid state."); -// if let StateReplyNft::All(state) = state_reply { -// println!("STATE: {:?}", state); -// } - -// let img_data = ImageData { -// limit_copies: Some(4), -// description: None, -// auto_changing_rules: None, -// }; -// let res = nft_collection.send( -// USERS[0], -// MusicNftAction::Expand { -// additional_links: vec![( -// Links { -// img_link: None, -// music_link: "New link".to_string(), -// }, -// img_data.clone(), -// )], -// }, -// ); -// assert!(!res.main_failed()); -// let res = nft_collection.send(USERS[2], MusicNftAction::Mint); -// assert!(!res.main_failed()); -// let res = nft_collection.send( -// USERS[2], -// MusicNftAction::Approve { -// to: USERS[3].into(), -// token_id: 5, -// }, -// ); -// assert!(!res.main_failed()); -// let state_reply = nft_collection -// .read_state(StateQueryNft::All) -// .expect("Unexpected invalid state."); -// if let StateReplyNft::All(state) = state_reply { -// println!("STATE: {:?}", state); -// } -// let res = nft_collection.send( -// USERS[2], -// MusicNftAction::Transfer { -// to: USERS[1].into(), -// token_id: 5, -// }, -// ); -// assert!(!res.main_failed()); -// let res = nft_collection.send( -// USERS[3], -// MusicNftAction::TransferFrom { -// from: USERS[1].into(), -// to: USERS[3].into(), -// token_id: 5, -// }, -// ); -// assert!(!res.main_failed()); -// let state_reply = nft_collection -// .read_state(StateQueryNft::All) -// .expect("Unexpected invalid state."); -// if let StateReplyNft::All(state) = state_reply { -// println!("STATE: {:?}", state); -// } - -// assert!(check_payload( -// 0, -// &res, -// "Target token_approvals is empty.".to_string() -// )); -// } - +use crate::utils::*; +use utils::prelude::*; +mod utils; +use gtest::Program; +use nft_marketplace_io::*; +use music_nft_io::{Config, ImageData, Links, ListenCapability, MusicNftAction, MusicNftError, StateQuery as StateQueryNft, StateReply as StateReplyNft, NftState}; +const USERS: &[u64] = &[5, 6, 7, 8]; + +#[test] +fn successful_basics() { + let sys = utils::initialize_system(); + let marketplace = Program::init_marketplace(&sys); + let nft_collection_code_id = + sys.submit_code("target/wasm32-unknown-unknown/debug/music_nft.opt.wasm"); + + let state_reply = marketplace + .read_state(StateQuery::CollectionsInfo) + .expect("Unexpected invalid state."); + if let StateReply::CollectionsInfo(state) = state_reply { + assert!(state.is_empty(), "Collection info should be empty"); + println!("Collection info: {:?}", state); + } + // Successful addition of a new collection + let name_music_nft = "Music NFT".to_string(); + marketplace.add_new_collection( + ADMINS[0], + nft_collection_code_id.into_bytes().into(), + name_music_nft.clone(), + None, + ); + + let state_reply = marketplace + .read_state(StateQuery::CollectionsInfo) + .expect("Unexpected invalid state."); + if let StateReply::CollectionsInfo(state) = state_reply { + assert!(!state.is_empty(), "Collection info shouldn't be empty"); + println!("Collection info: {:?}", state); + } + // Successful creation of a new collection + let init_music_nft_payload = get_init_music_nft_payload(USERS[0].into(), 0, Some(3), 0, None); + sys.mint_to(USERS[0], 100_000_000_000_000); + marketplace.create_collection( + USERS[0], + name_music_nft.clone(), + init_music_nft_payload.encode(), + 10_000_000_000_000, + None, + ); + + let state_reply = marketplace + .read_state(StateQuery::AllCollections) + .expect("Unexpected invalid state."); + let address_nft = if let StateReply::AllCollections(state) = state_reply { + assert!(!state.is_empty(), "Collections shouldn't be empty"); + println!("Collections: {:?}", state); + state[0].0 + } else { + assert!(false, "Unexpected StateReply variant"); + 0.into() + }; + + let address_nft_list: [u8; 32] = address_nft.into(); + let nft_collection = sys.get_program(address_nft_list); + let state_reply = nft_collection + .read_state(StateQueryNft::All) + .expect("Unexpected invalid state."); + if let StateReplyNft::All(state) = state_reply { + assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); + println!("Collection NFT info: {:?}", state); + } + + // Successful change config in the new collection + let config = Config { + name: "My Collection".to_string(), + description: "My Collection".to_string(), + collection_banner: "Collection banner".to_string(), + collection_logo: "Collection logo".to_string(), + collection_tags: vec!["tag1".to_string()], + additional_links: None, + royalty: 0, + user_mint_limit: 3.into(), + payment_for_mint: 0, + transferable: Some(0), + sellable: Some(0), + listening_capabilities: ListenCapability::Demo + }; + let res = nft_collection.send(USERS[0], MusicNftAction::ChangeConfig { config }); + assert!(!res.main_failed()); + let state_reply = nft_collection + .read_state(StateQueryNft::All) + .expect("Unexpected invalid state."); + if let StateReplyNft::All(state) = state_reply { + assert_eq!(state.config.name, "My Collection".to_string()); + assert_eq!(state.config.description, "My Collection".to_string()); + } + + // Successful mint NFT in the new collection + marketplace.mint(USERS[1], address_nft, None); + + let state_reply = nft_collection + .read_state(StateQueryNft::All) + .expect("Unexpected invalid state."); + if let StateReplyNft::All(state) = state_reply { + println!("Collection NFT info: {:?}", state); + assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); + let (owner, token_id) = state.owners.get(0).expect("Can't be None"); + assert_eq!(*owner, USERS[1].into(), "Wrong owner"); + assert_eq!(*token_id, vec![0], "Wrong token id"); + } + + // Successful transfer NFT in the collection + let res = nft_collection.send( + USERS[1], + MusicNftAction::Transfer { + to: USERS[2].into(), + token_id: 0, + }, + ); + assert!(!res.main_failed()); + + let state_reply = nft_collection + .read_state(StateQueryNft::All) + .expect("Unexpected invalid state."); + if let StateReplyNft::All(state) = state_reply { + println!("!!!!!!!!!! STATE: {:?}", state); + let (owner, token_id) = state.owners.get(0).expect("Can't be None"); + assert_eq!(*owner, USERS[2].into(), "Wrong owner"); + assert_eq!(*token_id, vec![0], "Wrong token id"); + } + + // Successful approve NFT in the collection (USERS[2] -Approve-> USERS[1]) + let res = nft_collection.send( + USERS[2], + MusicNftAction::Approve { + to: USERS[1].into(), + token_id: 0, + }, + ); + assert!(!res.main_failed()); + + let state_reply = nft_collection + .read_state(StateQueryNft::All) + .expect("Unexpected invalid state."); + if let StateReplyNft::All(state) = state_reply { + let (token_id, approval) = state.token_approvals.get(0).expect("Can't be None"); + assert_eq!(*token_id, 0, "Wrong owner"); + assert_eq!(*approval, USERS[1].into(), "Wrong token id"); + } + // Check approve (USERS[1] -Transfer-> USERS[3]) + let res = nft_collection.send( + USERS[1], + MusicNftAction::TransferFrom { + from: USERS[2].into(), + to: USERS[3].into(), + token_id: 0, + }, + ); + assert!(!res.main_failed()); + + let state_reply = nft_collection + .read_state(StateQueryNft::All) + .expect("Unexpected invalid state."); + if let StateReplyNft::All(state) = state_reply { + println!("STATE OWNERS: {:?}", state.owners); + let (owner, token_id) = state.owners.get(0).expect("Can't be None"); + assert_eq!(*owner, USERS[3].into(), "Wrong owner"); + assert_eq!(*token_id, vec![0], "Wrong token id"); + } + + // Check limit of mint = 3 + marketplace.mint(USERS[3], address_nft, None); + marketplace.mint(USERS[3], address_nft, None); + marketplace.mint(USERS[3], address_nft, None); + marketplace.mint( + USERS[3], + address_nft, + Some(NftMarketplaceError::ErrorFromCollection), + ); + + // Successful Expand NFT in the collection + let img_data = ImageData { + limit_copies: Some(1), + description: None + }; + let additional_links = vec![(Links{ + img_link: None, + music_link: "add_link_1".to_string() + }, img_data.clone()), + (Links{ + img_link: None, + music_link: "add_link_2".to_string() + }, img_data.clone()), + ]; + let res = nft_collection.send( + USERS[0], + MusicNftAction::Expand { + additional_links, + }, + ); + assert!(!res.main_failed()); + let state_reply = nft_collection + .read_state(StateQueryNft::All) + .expect("Unexpected invalid state."); + if let StateReplyNft::All(state) = state_reply { + assert_eq!( + state.links_and_data.len(), + 8, + "Wrong length of img_links_and_data" + ); + println!("STATE: {:?}", state); + } +} + +#[test] +fn failures() { + let sys = utils::initialize_system(); + let marketplace = Program::init_marketplace(&sys); + let nft_collection_code_id = + sys.submit_code("target/wasm32-unknown-unknown/debug/music_nft.opt.wasm"); + + let name_simple_nft = "Simple NFT".to_string(); + marketplace.add_new_collection( + ADMINS[0], + nft_collection_code_id.into_bytes().into(), + name_simple_nft.clone(), + None, + ); + + // The mint limit must be greater than zero + let mut init_music_nft_payload = get_init_music_nft_payload(USERS[0].into(), 0, Some(0), 0, None); + sys.mint_to(USERS[0], 100_000_000_000_000); + marketplace.create_collection( + USERS[0], + name_simple_nft.clone(), + init_music_nft_payload.encode(), + 10_000_000_000_000, + Some(NftMarketplaceError::CreationError), + ); + sys.claim_value_from_mailbox(USERS[0]); + let balance = sys.balance_of(USERS[0]); + assert_eq!(balance, 100_000_000_000_000, "Wrong balance"); + + // There must be at least one link to create a collection + init_music_nft_payload.config.user_mint_limit = 4.into(); + init_music_nft_payload.links_and_data = vec![]; + marketplace.create_collection( + USERS[0], + name_simple_nft.clone(), + init_music_nft_payload.encode(), + 10_000_000_000_000, + Some(NftMarketplaceError::CreationError), + ); + + sys.claim_value_from_mailbox(USERS[0]); + let balance = sys.balance_of(USERS[0]); + assert_eq!(balance, 100_000_000_000_000, "Wrong balance"); + + // Limit of copies value is equal to 0 + let img_data = ImageData { + limit_copies: Some(0), + description: None + }; + let links = Links{ + img_link: None, + music_link: "Music link-0".to_owned() + }; + init_music_nft_payload.links_and_data = vec![(links, img_data.clone())]; + marketplace.create_collection( + USERS[0], + name_simple_nft.clone(), + init_music_nft_payload.encode(), + 10_000_000_000_000, + Some(NftMarketplaceError::CreationError), + ); + sys.claim_value_from_mailbox(USERS[0]); + let balance = sys.balance_of(USERS[0]); + assert_eq!(balance, 100_000_000_000_000, "Wrong balance"); + + let img_data = ImageData { + limit_copies: Some(1), + description: None, + }; + let links_and_data: Vec<(Links, ImageData)> = (0..5) + .map(|i| { + let links = Links { + img_link: Some(format!("Img-{}", i)), + music_link: format!("Music link-{}", i), + }; + (links, img_data.clone()) + }) + .collect(); + + init_music_nft_payload.links_and_data = links_and_data; + marketplace.create_collection( + USERS[0], + name_simple_nft.clone(), + init_music_nft_payload.encode(), + 10_000_000_000_000, + None, + ); + + let state_reply = marketplace + .read_state(StateQuery::AllCollections) + .expect("Unexpected invalid state."); + let address_nft = if let StateReply::AllCollections(state) = state_reply { + assert!(!state.is_empty(), "Collections shouldn't be empty"); + println!("Collections: {:?}", state); + state[0].0 + } else { + assert!(false, "Unexpected StateReply variant"); + 0.into() + }; + + let address_nft_list: [u8; 32] = address_nft.into(); + let nft_collection = sys.get_program(address_nft_list); + + for _ in 0..4 { + marketplace.mint(USERS[1], address_nft, None); + } + marketplace.mint( + USERS[1], + address_nft, + Some(NftMarketplaceError::ErrorFromCollection), + ); + marketplace.mint(USERS[2], address_nft, None); + marketplace.mint( + USERS[2], + address_nft, + Some(NftMarketplaceError::ErrorFromCollection), + ); + + let config = Config { + name: "User Collection".to_string(), + description: "User Collection".to_string(), + collection_banner: "Collection banner".to_string(), + collection_logo: "Collection logo".to_string(), + collection_tags: vec!["tag1".to_string()], + additional_links: None, + royalty: 0, + user_mint_limit: 3.into(), + payment_for_mint: 0, + transferable: Some(0), + sellable: Some(0), + listening_capabilities: ListenCapability::Demo + }; + let res = nft_collection.send(USERS[0], MusicNftAction::ChangeConfig { config }); + check_music_nft_error(USERS[0], &res, MusicNftError::ConfigCannotBeChanged); + assert!(!res.main_failed()); + + let state_reply = nft_collection + .read_state(StateQueryNft::All) + .expect("Unexpected invalid state."); + if let StateReplyNft::All(state) = state_reply { + println!("STATE: {:?}", state); + } + + let img_data = ImageData { + limit_copies: Some(4), + description: None + }; + let additional_links = vec![(Links{ + img_link: None, + music_link: "New_img".to_owned() + }, img_data.clone())]; + let res = nft_collection.send( + USERS[0], + MusicNftAction::Expand { + additional_links, + }, + ); + assert!(!res.main_failed()); + marketplace.mint(USERS[2], address_nft, None); + + let res = nft_collection.send( + USERS[2], + MusicNftAction::Approve { + to: USERS[3].into(), + token_id: 5, + }, + ); + assert!(!res.main_failed()); + let state_reply = nft_collection + .read_state(StateQueryNft::All) + .expect("Unexpected invalid state."); + if let StateReplyNft::All(state) = state_reply { + println!("STATE: {:?}", state); + } + let res = nft_collection.send( + USERS[2], + MusicNftAction::Transfer { + to: USERS[1].into(), + token_id: 5, + }, + ); + assert!(!res.main_failed()); + let res = nft_collection.send( + USERS[3], + MusicNftAction::TransferFrom { + from: USERS[1].into(), + to: USERS[3].into(), + token_id: 5, + }, + ); + assert!(!res.main_failed()); + let state_reply = nft_collection + .read_state(StateQueryNft::All) + .expect("Unexpected invalid state."); + if let StateReplyNft::All(state) = state_reply { + println!("STATE: {:?}", state); + } + check_music_nft_error(USERS[3], &res, MusicNftError::AccessDenied); +} + +#[test] +fn check_transferable() { + let sys = utils::initialize_system(); + let marketplace = Program::init_marketplace(&sys); + let nft_collection_code_id = + sys.submit_code("target/wasm32-unknown-unknown/debug/music_nft.opt.wasm"); + + // Successful addition of a new collection + let name_simple_nft = "Simple NFT".to_string(); + marketplace.add_new_collection( + ADMINS[0], + nft_collection_code_id.into_bytes().into(), + name_simple_nft.clone(), + None, + ); + + let mut init_music_nft_payload = get_init_music_nft_payload(USERS[0].into(), 0, Some(3), 0, None); + let transferable_time = 9_000; + init_music_nft_payload.config.transferable = Some(transferable_time); + sys.mint_to(USERS[0], 100_000_000_000_000); + marketplace.create_collection( + USERS[0], + name_simple_nft.clone(), + init_music_nft_payload.encode(), + 10_000_000_000_000, + None, + ); + let state_reply = marketplace + .read_state(StateQuery::AllCollections) + .expect("Unexpected invalid state."); + let address_nft = if let StateReply::AllCollections(state) = state_reply { + assert!(!state.is_empty(), "Collections shouldn't be empty"); + println!("Collections: {:?}", state); + state[0].0 + } else { + assert!(false, "Unexpected StateReply variant"); + 0.into() + }; + + let address_nft_list: [u8; 32] = address_nft.into(); + let nft_collection = sys.get_program(address_nft_list); + let state_reply = nft_collection + .read_state(StateQueryNft::All) + .expect("Unexpected invalid state."); + if let StateReplyNft::All(state) = state_reply { + assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); + println!("Collection NFT info: {:?}", state); + } + + // Successful mint NFT in the new collection + marketplace.mint(USERS[1], address_nft, None); + + // Transfer NFT in the collection + let res = nft_collection.send( + USERS[1], + MusicNftAction::Transfer { + to: USERS[2].into(), + token_id: 0, + }, + ); + assert!(!res.main_failed()); + check_music_nft_error(USERS[1], &res, MusicNftError::NotTransferable); + + sys.spend_blocks(3); + + let res = nft_collection.send( + USERS[1], + MusicNftAction::Transfer { + to: USERS[2].into(), + token_id: 0, + }, + ); + assert!(!res.main_failed()); + let state_reply = nft_collection + .read_state(StateQueryNft::All) + .expect("Unexpected invalid state."); + if let StateReplyNft::All(state) = state_reply { + println!("!!!!!!!!!! STATE: {:?}", state); + let (owner, token_id) = state.owners.get(0).expect("Can't be None"); + assert_eq!(*owner, USERS[2].into(), "Wrong owner"); + assert_eq!(*token_id, vec![0], "Wrong token id"); + } +} + +#[test] +fn check_payment_for_mint() { + let sys = utils::initialize_system(); + let marketplace = Program::init_marketplace(&sys); + let nft_collection_code_id = + sys.submit_code("target/wasm32-unknown-unknown/debug/music_nft.opt.wasm"); + + let state_reply = marketplace + .read_state(StateQuery::CollectionsInfo) + .expect("Unexpected invalid state."); + if let StateReply::CollectionsInfo(state) = state_reply { + assert!(state.is_empty(), "Collection info should be empty"); + println!("Collection info: {:?}", state); + } + // Successful addition of a new collection + let name_simple_nft = "Simple NFT".to_string(); + marketplace.add_new_collection( + ADMINS[0], + nft_collection_code_id.into_bytes().into(), + name_simple_nft.clone(), + None, + ); + + let state_reply = marketplace + .read_state(StateQuery::CollectionsInfo) + .expect("Unexpected invalid state."); + if let StateReply::CollectionsInfo(state) = state_reply { + assert!(!state.is_empty(), "Collection info shouldn't be empty"); + println!("Collection info: {:?}", state); + } + + // The payment for mint must be greater than existential deposit (10000000000000) + let mut init_music_nft_payload = get_init_music_nft_payload(USERS[0].into(), 0, Some(3), 0, None); + let payment_for_mint = 9_000_000_000_000; + init_music_nft_payload.config.payment_for_mint = payment_for_mint; + sys.mint_to(USERS[0], 100_000_000_000_000); + marketplace.create_collection( + USERS[0], + name_simple_nft.clone(), + init_music_nft_payload.encode(), + 10_000_000_000_000, + Some(NftMarketplaceError::CreationError), + ); + sys.claim_value_from_mailbox(USERS[0]); + let balance = sys.balance_of(USERS[0]); + assert_eq!(balance, 100_000_000_000_000, "Wrong balance"); + + // Successful creation of a new collection + let payment_for_mint = 10_000_000_000_000; + init_music_nft_payload.config.payment_for_mint = payment_for_mint; + marketplace.create_collection( + USERS[0], + name_simple_nft.clone(), + init_music_nft_payload.encode(), + 10_000_000_000_000, + None, + ); + + let state_reply = marketplace + .read_state(StateQuery::AllCollections) + .expect("Unexpected invalid state."); + let address_nft = if let StateReply::AllCollections(state) = state_reply { + assert!(!state.is_empty(), "Collections shouldn't be empty"); + println!("Collections: {:?}", state); + state[0].0 + } else { + assert!(false, "Unexpected StateReply variant"); + 0.into() + }; + + let address_nft_list: [u8; 32] = address_nft.into(); + let nft_collection = sys.get_program(address_nft_list); + let state_reply = nft_collection + .read_state(StateQueryNft::All) + .expect("Unexpected invalid state."); + if let StateReplyNft::All(state) = state_reply { + assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); + println!("Collection NFT info: {:?}", state); + } + + sys.mint_to(USERS[1], 3 * payment_for_mint); + let payment_for_mint_with_percent = payment_for_mint + payment_for_mint * 200 as u128 / 10_000; + // Successful mint NFT in the new collection + let res = marketplace.send_with_value( + USERS[1], + NftMarketplaceAction::Mint { + collection_address: address_nft, + }, + payment_for_mint_with_percent, + ); + assert!(!res.main_failed()); + + let old_balance = sys.balance_of(USERS[0]); + sys.claim_value_from_mailbox(USERS[0]); + let balance = sys.balance_of(USERS[0]); + assert_eq!(balance - old_balance, payment_for_mint, "Wrong balance"); + + // Wrong Value + let res = marketplace.send_with_value( + USERS[1], + NftMarketplaceAction::Mint { + collection_address: address_nft, + }, + payment_for_mint, + ); + + let result = &res.decoded_log::>(); + println!("RES: {:?}", result); + + assert!(!res.main_failed()); + assert!(res.contains(&( + USERS[1], + Err::(NftMarketplaceError::WrongValue).encode() + ))); +} + +#[test] +fn permission_to_mint() { + let sys = utils::initialize_system(); + let marketplace = Program::init_marketplace(&sys); + let nft_collection_code_id = + sys.submit_code("target/wasm32-unknown-unknown/debug/music_nft.opt.wasm"); + + let state_reply = marketplace + .read_state(StateQuery::CollectionsInfo) + .expect("Unexpected invalid state."); + if let StateReply::CollectionsInfo(state) = state_reply { + assert!(state.is_empty(), "Collection info should be empty"); + println!("Collection info: {:?}", state); + } + // Successful addition of a new collection + let name_simple_nft = "Simple NFT".to_string(); + marketplace.add_new_collection( + ADMINS[0], + nft_collection_code_id.into_bytes().into(), + name_simple_nft.clone(), + None, + ); + let state_reply = marketplace + .read_state(StateQuery::CollectionsInfo) + .expect("Unexpected invalid state."); + if let StateReply::CollectionsInfo(state) = state_reply { + assert!(!state.is_empty(), "Collection info shouldn't be empty"); + println!("Collection info: {:?}", state); + } + // Successful creation of a new collection + let init_music_nft_payload = get_init_music_nft_payload(USERS[0].into(), 0, Some(3), 0, Some(vec![])); + sys.mint_to(USERS[0], 100_000_000_000_000); + marketplace.create_collection( + USERS[0], + name_simple_nft.clone(), + init_music_nft_payload.encode(), + 10_000_000_000_000, + None, + ); + + let state_reply = marketplace + .read_state(StateQuery::AllCollections) + .expect("Unexpected invalid state."); + let address_nft = if let StateReply::AllCollections(state) = state_reply { + assert!(!state.is_empty(), "Collections shouldn't be empty"); + println!("Collections: {:?}", state); + state[0].0 + } else { + assert!(false, "Unexpected StateReply variant"); + 0.into() + }; + + let address_nft_list: [u8; 32] = address_nft.into(); + let nft_collection = sys.get_program(address_nft_list); + let state_reply = nft_collection + .read_state(StateQueryNft::All) + .expect("Unexpected invalid state."); + if let StateReplyNft::All(state) = state_reply { + assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); + } + + // Succes mint NFT from admin + marketplace.mint(USERS[0], address_nft, None); + + // Fail mint NFT from user + marketplace.mint( + USERS[1], + address_nft, + Some(NftMarketplaceError::ErrorFromCollection), + ); + + let res = nft_collection.send( + USERS[0], + MusicNftAction::AddUsersForMint { + users: vec![USERS[1].into()], + }, + ); + assert!(!res.main_failed()); + + // Success mint + marketplace.mint(USERS[1], address_nft, None); + + let state_reply = nft_collection + .read_state(StateQueryNft::All) + .expect("Unexpected invalid state."); + if let StateReplyNft::All(state) = state_reply { + println!("Collection NFT info: {:?}", state); + assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); + let (owner, token_id) = state.owners.get(1).expect("Can't be None"); + assert_eq!(*owner, USERS[1].into(), "Wrong owner"); + assert_eq!(*token_id, vec![1], "Wrong token id"); + } + let res = nft_collection.send( + USERS[0], + MusicNftAction::DeleteUserForMint { + user: USERS[1].into(), + }, + ); + assert!(!res.main_failed()); + + // Fail mint NFT in the new collection + marketplace.mint( + USERS[1], + address_nft, + Some(NftMarketplaceError::ErrorFromCollection), + ); +} + +fn get_state(nft_collection: &Program) -> Option { + let state_reply = nft_collection + .read_state(StateQueryNft::All) + .expect("Unexpected invalid state."); + if let StateReplyNft::All(state) = state_reply { + return Some(state); + } + None +} + +// TODO // #[test] -// fn check_auto_changing_rules() { -// let sys = utils::initialize_system(); -// init_marketplace(&sys); -// let marketplace = sys.get_program(1); -// let nft_collection_code_id = -// sys.submit_code("target/wasm32-unknown-unknown/debug/music_nft.opt.wasm"); - -// let state_reply = marketplace -// .read_state(StateQuery::CollectionsInfo) -// .expect("Unexpected invalid state."); -// if let StateReply::CollectionsInfo(state) = state_reply { -// assert!(state.is_empty(), "Collection info should be empty"); -// println!("Collection info: {:?}", state); -// } -// // Successful addition of a new collection -// let name_simple_nft = "Simple NFT".to_string(); -// let res = add_new_collection( -// &marketplace, -// ADMINS[0], -// nft_collection_code_id.into_bytes().into(), -// name_simple_nft.clone(), -// ); -// assert!(!res.main_failed()); -// let state_reply = marketplace -// .read_state(StateQuery::CollectionsInfo) -// .expect("Unexpected invalid state."); -// if let StateReply::CollectionsInfo(state) = state_reply { -// assert!(!state.is_empty(), "Collection info shouldn't be empty"); -// println!("Collection info: {:?}", state); -// } -// let img_data = ImageData { -// limit_copies: Some(1), -// description: None, -// auto_changing_rules: Some(vec![ -// (9, Action::ChangeImg("Auto change image".to_string())), -// (18, Action::AddMeta("Auto change metadata".to_string())), -// ]), -// }; -// let links_and_data: Vec<(Links, ImageData)> = (0..10) -// .map(|i| { -// ( -// Links { -// img_link: None, -// music_link: format!("Img-{}", i), -// }, -// img_data.clone(), -// ) -// }) -// .collect(); - -// let additional_links = Some(AdditionalLinks { -// external_url: Some("External link".to_string()), -// telegram: None, -// xcom: None, -// medium: None, -// discord: None, -// }); - -// // Successful creation of a new collection -// let init_nft_payload = MusicNftInit { -// collection_owner: USERS[0].into(), -// config: Config { -// name: "User Collection".to_string(), -// description: "User Collection".to_string(), -// collection_banner: "Collection banner".to_string(), -// collection_logo: "Collection logo".to_string(), -// collection_tags: vec!["tag1".to_string()], -// additional_links, -// royalty: 0, -// user_mint_limit: 3.into(), -// listening_capabilities: ListenCapability::Demo, -// payment_for_mint: 0, -// transferable: Some(0), -// sellable: Some(0), -// }, -// links_and_data, -// }; -// let res = create_collection( -// &marketplace, -// USERS[0], -// name_simple_nft.clone(), -// init_nft_payload.encode(), -// ); -// assert!(!res.main_failed()); -// let result = &res.decoded_log::>(); -// println!("RES: {:?}", result); -// let state_reply = marketplace -// .read_state(StateQuery::AllCollections) -// .expect("Unexpected invalid state."); -// let address_nft = if let StateReply::AllCollections(state) = state_reply { -// assert!(!state.is_empty(), "Collections shouldn't be empty"); -// println!("Collections: {:?}", state); -// state[0].0 -// } else { -// assert!(false, "Unexpected StateReply variant"); -// 0.into() -// }; - -// let address_nft: [u8; 32] = address_nft.into(); -// let nft_collection = sys.get_program(address_nft); -// let state_reply = nft_collection -// .read_state(StateQueryNft::All) -// .expect("Unexpected invalid state."); -// if let StateReplyNft::All(state) = state_reply { -// assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); -// println!("Collection NFT info: {:?}", state); -// } - -// // Successful mint NFT in the new collection -// let res = nft_collection.send(USERS[1], MusicNftAction::Mint); -// assert!(!res.main_failed()); - -// let state_reply = nft_collection -// .read_state(StateQueryNft::All) -// .expect("Unexpected invalid state."); -// if let StateReplyNft::All(state) = state_reply { -// println!("Collection NFT info: {:?}", state); -// assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); -// let (owner, token_id) = state.owners.get(0).expect("Can't be None"); -// assert_eq!(*owner, USERS[1].into(), "Wrong owner"); -// assert_eq!(*token_id, vec![0], "Wrong token id"); -// } - -// let state = get_state(&nft_collection).unwrap(); -// assert_ne!(state.tokens[0].1.img_link, "Auto change image".to_string()); -// assert_ne!( -// state.tokens[0].1.metadata, -// vec!["Auto change metadata".to_string()] -// ); -// sys.spend_blocks(3); -// let state = get_state(&nft_collection).unwrap(); -// assert_eq!(state.tokens[0].1.img_link, "Auto change image".to_string()); -// assert_ne!( -// state.tokens[0].1.metadata, -// vec!["Auto change metadata".to_string()] -// ); -// sys.spend_blocks(6); -// let state = get_state(&nft_collection).unwrap(); -// assert_eq!(state.tokens[0].1.img_link, "Auto change image".to_string()); -// assert_eq!( -// state.tokens[0].1.metadata, -// vec!["Auto change metadata".to_string()] -// ); -// } +// fn admin_features() { -// #[test] -// fn check_transferable() { -// let sys = utils::initialize_system(); -// init_marketplace(&sys); -// let marketplace = sys.get_program(1); -// let nft_collection_code_id = -// sys.submit_code("target/wasm32-unknown-unknown/debug/music_nft.opt.wasm"); - -// // Successful addition of a new collection -// let name_simple_nft = "Simple NFT".to_string(); -// let res = add_new_collection( -// &marketplace, -// ADMINS[0], -// nft_collection_code_id.into_bytes().into(), -// name_simple_nft.clone(), -// ); -// assert!(!res.main_failed()); - -// let img_data = ImageData { -// limit_copies: Some(1), -// description: None, -// auto_changing_rules: None, -// }; -// let links_and_data: Vec<(Links, ImageData)> = (0..10) -// .map(|i| { -// ( -// Links { -// img_link: None, -// music_link: format!("Img-{}", i), -// }, -// img_data.clone(), -// ) -// }) -// .collect(); - -// let additional_links = Some(AdditionalLinks { -// external_url: Some("External link".to_string()), -// telegram: None, -// xcom: None, -// medium: None, -// discord: None, -// }); - -// // Successful creation of a new collection -// let mut init_nft_payload = MusicNftInit { -// collection_owner: USERS[0].into(), -// config: Config { -// name: "User Collection".to_string(), -// description: "User Collection".to_string(), -// collection_banner: "Collection banner".to_string(), -// collection_logo: "Collection logo".to_string(), -// collection_tags: vec!["tag1".to_string()], -// additional_links, -// royalty: 0, -// user_mint_limit: 3.into(), -// listening_capabilities: ListenCapability::Demo, -// payment_for_mint: 0, -// transferable: Some(0), -// sellable: Some(0), -// }, -// links_and_data, -// }; - -// let transferable_time = 9_000; -// init_nft_payload.config.transferable = Some(transferable_time); -// let res = create_collection( -// &marketplace, -// USERS[0], -// name_simple_nft.clone(), -// init_nft_payload.encode(), -// ); -// assert!(!res.main_failed()); -// let result = &res.decoded_log::>(); -// println!("RES: {:?}", result); -// let state_reply = marketplace -// .read_state(StateQuery::AllCollections) -// .expect("Unexpected invalid state."); -// let address_nft = if let StateReply::AllCollections(state) = state_reply { -// assert!(!state.is_empty(), "Collections shouldn't be empty"); -// println!("Collections: {:?}", state); -// state[0].0 -// } else { -// assert!(false, "Unexpected StateReply variant"); -// 0.into() -// }; - -// let address_nft: [u8; 32] = address_nft.into(); -// let nft_collection = sys.get_program(address_nft); -// let state_reply = nft_collection -// .read_state(StateQueryNft::All) -// .expect("Unexpected invalid state."); -// if let StateReplyNft::All(state) = state_reply { -// assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); -// println!("Collection NFT info: {:?}", state); -// } - -// // Successful mint NFT in the new collection -// let res = nft_collection.send(USERS[1], MusicNftAction::Mint); -// assert!(!res.main_failed()); - -// // Transfer NFT in the collection -// let res = nft_collection.send( -// USERS[1], -// MusicNftAction::Transfer { -// to: USERS[2].into(), -// token_id: 0, -// }, -// ); -// assert!(!res.main_failed()); -// assert!(check_payload( -// 0, -// &res, -// "NonFungibleToken: transfer will be available after the deadline".to_string() -// )); - -// sys.spend_blocks(3); - -// let res = nft_collection.send( -// USERS[1], -// MusicNftAction::Transfer { -// to: USERS[2].into(), -// token_id: 0, -// }, -// ); -// assert!(!res.main_failed()); -// let state_reply = nft_collection -// .read_state(StateQueryNft::All) -// .expect("Unexpected invalid state."); -// if let StateReplyNft::All(state) = state_reply { -// println!("!!!!!!!!!! STATE: {:?}", state); -// let (owner, token_id) = state.owners.get(0).expect("Can't be None"); -// assert_eq!(*owner, USERS[2].into(), "Wrong owner"); -// assert_eq!(*token_id, vec![0], "Wrong token id"); -// } // } // #[test] -// fn check_payment_for_mint() { -// let sys = utils::initialize_system(); -// init_marketplace(&sys); -// let marketplace = sys.get_program(1); -// let nft_collection_code_id = -// sys.submit_code("target/wasm32-unknown-unknown/debug/music_nft.opt.wasm"); - -// let state_reply = marketplace -// .read_state(StateQuery::CollectionsInfo) -// .expect("Unexpected invalid state."); -// if let StateReply::CollectionsInfo(state) = state_reply { -// assert!(state.is_empty(), "Collection info should be empty"); -// println!("Collection info: {:?}", state); -// } -// // Successful addition of a new collection -// let name_simple_nft = "Simple NFT".to_string(); -// let res = add_new_collection( -// &marketplace, -// ADMINS[0], -// nft_collection_code_id.into_bytes().into(), -// name_simple_nft.clone(), -// ); -// assert!(!res.main_failed()); -// let state_reply = marketplace -// .read_state(StateQuery::CollectionsInfo) -// .expect("Unexpected invalid state."); -// if let StateReply::CollectionsInfo(state) = state_reply { -// assert!(!state.is_empty(), "Collection info shouldn't be empty"); -// println!("Collection info: {:?}", state); -// } - -// // The payment for mint must be greater than existential deposit (10000000000000) -// let img_data = ImageData { -// limit_copies: Some(1), -// description: None, -// auto_changing_rules: None, -// }; -// let links_and_data: Vec<(Links, ImageData)> = (0..10) -// .map(|i| { -// ( -// Links { -// img_link: None, -// music_link: format!("Img-{}", i), -// }, -// img_data.clone(), -// ) -// }) -// .collect(); - -// let additional_links = Some(AdditionalLinks { -// external_url: Some("External link".to_string()), -// telegram: None, -// xcom: None, -// medium: None, -// discord: None, -// }); - -// // Successful creation of a new collection -// let mut init_nft_payload = MusicNftInit { -// collection_owner: USERS[0].into(), -// config: Config { -// name: "User Collection".to_string(), -// description: "User Collection".to_string(), -// collection_banner: "Collection banner".to_string(), -// collection_logo: "Collection logo".to_string(), -// collection_tags: vec!["tag1".to_string()], -// additional_links, -// royalty: 0, -// user_mint_limit: 3.into(), -// listening_capabilities: ListenCapability::Demo, -// payment_for_mint: 0, -// transferable: Some(0), -// sellable: Some(0), -// }, -// links_and_data, -// }; - -// let payment_for_mint = 9_000_000_000_000; -// init_nft_payload.config.payment_for_mint = payment_for_mint; -// let res = create_collection( -// &marketplace, -// USERS[0], -// name_simple_nft.clone(), -// init_nft_payload.encode(), -// ); -// assert!(res.main_failed()); - -// // Successful creation of a new collection -// let payment_for_mint = 11_000_000_000_000; -// init_nft_payload.config.payment_for_mint = payment_for_mint; -// let res = create_collection( -// &marketplace, -// USERS[0], -// name_simple_nft.clone(), -// init_nft_payload.encode(), -// ); -// assert!(!res.main_failed()); - -// let result = &res.decoded_log::>(); +// fn check() { +// let input = "000080ee3600000000008c9de8de3b0000000000000000000000c800c800"; +// let decoded = hex::decode(input).expect("Decoding failed"); +// let mut res: &[u8] = &decoded; +// let result = Result::::decode(&mut res).ok(); // println!("RES: {:?}", result); -// let state_reply = marketplace -// .read_state(StateQuery::AllCollections) -// .expect("Unexpected invalid state."); -// let address_nft = if let StateReply::AllCollections(state) = state_reply { -// assert!(!state.is_empty(), "Collections shouldn't be empty"); -// println!("Collections: {:?}", state); -// state[0].0 -// } else { -// assert!(false, "Unexpected StateReply variant"); -// 0.into() -// }; - -// let address_nft: [u8; 32] = address_nft.into(); -// let nft_collection = sys.get_program(address_nft); -// let state_reply = nft_collection -// .read_state(StateQueryNft::All) -// .expect("Unexpected invalid state."); -// if let StateReplyNft::All(state) = state_reply { -// assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); -// println!("Collection NFT info: {:?}", state); -// } - -// // Successful mint NFT in the new collection -// sys.mint_to(USERS[1], 2 * payment_for_mint); -// let res = nft_collection.send_with_value(USERS[1], MusicNftAction::Mint, payment_for_mint); -// assert!(!res.main_failed()); - -// sys.claim_value_from_mailbox(USERS[0]); -// let balance = sys.balance_of(USERS[0]); -// assert_eq!(balance, payment_for_mint, "Wrong balance"); -// let res = nft_collection.send_with_value(USERS[1], MusicNftAction::Mint, payment_for_mint - 1); -// assert!(!res.main_failed()); - -// assert!(check_payload( -// 0, -// &res, -// "Incorrectly entered mint fee.".to_string() -// )); -// } - -// fn get_state(nft_collection: &Program) -> Option { -// let state_reply = nft_collection -// .read_state(StateQueryNft::All) -// .expect("Unexpected invalid state."); -// if let StateReplyNft::All(state) = state_reply { -// return Some(state); -// } -// None // } diff --git a/tests/utils/mod.rs b/tests/utils/mod.rs index 99ae554..0a8ed83 100644 --- a/tests/utils/mod.rs +++ b/tests/utils/mod.rs @@ -1,9 +1,11 @@ use gstd::{ActorId, CodeId, Encode}; use gtest::{Program, RunResult, System}; use nft_io::{Config, ImageData, NftError, NftEvent, NftInit}; +use music_nft_io::{MusicNftInit, MusicNftError, MusicNftEvent, Links, Config as MusicConfig, ListenCapability, ImageData as MusicImageData}; use nft_marketplace_io::{ NftMarketplaceAction, NftMarketplaceError, NftMarketplaceEvent, NftMarketplaceInit, Offer, }; +use crate::codec::Error; mod common; pub mod prelude; @@ -138,6 +140,10 @@ impl NftMarketplace for Program<'_> { }; let res = marketplace.send(ADMINS[0], init_payload); assert!(!res.main_failed()); + let res = marketplace.send(ADMINS[0], NftMarketplaceAction::AllowMessage(true)); + assert!(!res.main_failed()); + let res = marketplace.send(ADMINS[0], NftMarketplaceAction::AllowCreateCollection(true)); + assert!(!res.main_failed()); marketplace } fn add_new_collection( @@ -157,6 +163,7 @@ impl NftMarketplace for Program<'_> { meta_link: meta_link.clone(), type_name: type_name.clone(), type_description: type_description.clone(), + allow_create: true }, ); assert!(!res.main_failed()); @@ -522,6 +529,10 @@ impl NftMarketplace for Program<'_> { pub fn check_nft_error(from: u64, result: &RunResult, error: NftError) { assert!(result.contains(&(from, Err::(error).encode()))); } +pub fn check_music_nft_error(from: u64, result: &RunResult, error: MusicNftError) { + assert!(result.contains(&(from, Err::(error).encode()))); +} + pub fn get_init_nft_payload( collection_owner: ActorId, @@ -556,6 +567,48 @@ pub fn get_init_nft_payload( permission_to_mint, } } + +pub fn get_init_music_nft_payload( + collection_owner: ActorId, + royalty: u16, + user_mint_limit: Option, + payment_for_mint: u128, + permission_to_mint: Option>, +) -> MusicNftInit { + let img_data = MusicImageData { + limit_copies: Some(1), + description: None, + }; + let links_and_data: Vec<(Links, MusicImageData)> = (0..10) + .map(|i| { + let links = Links { + img_link: Some(format!("Img-{}", i)), + music_link: format!("Music link-{}", i), + }; + (links, img_data.clone()) + }) + .collect(); + + MusicNftInit { + collection_owner, + config: MusicConfig { + name: "User Collection".to_string(), + description: "User Collection".to_string(), + collection_banner: "Collection banner".to_string(), + collection_logo: "Collection logo".to_string(), + collection_tags: vec!["tag1".to_string()], + additional_links: None, + royalty, + user_mint_limit, + payment_for_mint, + transferable: Some(0), + sellable: Some(0), + listening_capabilities: ListenCapability::Demo + }, + links_and_data, + permission_to_mint, + } +} // pub fn get_state( // marketplace: &Program, // admin: u64, diff --git a/tests/utils_gclient/mod.rs b/tests/utils_gclient/mod.rs index bca0141..44ff1b6 100644 --- a/tests/utils_gclient/mod.rs +++ b/tests/utils_gclient/mod.rs @@ -89,7 +89,7 @@ pub async fn init_marketplace(api: &GearApi) -> Result<(MessageId, ProgramId), ( royalty_to_marketplace_for_trade: royalty_to_marketplace, royalty_to_marketplace_for_mint: royalty_to_marketplace, ms_in_block: 3_000, - fee_per_uploaded_file: 257_142_857_100, + fee_per_uploaded_file: 282_857_142_900, max_creator_royalty: 1_000, max_number_of_images: 10_000, } @@ -118,6 +118,54 @@ pub async fn init_marketplace(api: &GearApi) -> Result<(MessageId, ProgramId), ( ) .await .expect("Error upload program bytes"); + + let allow_message_payload = NftMarketplaceAction::AllowMessage(true); + + let gas_info = api + .calculate_handle_gas( + None, + program_id, + allow_message_payload.encode(), + 0, + true, + ) + .await + .expect("Error calculate gas"); + + let (message_id, _) = api + .send_message( + program_id, + allow_message_payload, + gas_info.min_limit, + 0, + ) + .await + .expect("Error send message"); + + + let allow_create_collection_payload = NftMarketplaceAction::AllowCreateCollection(true); + + let gas_info = api + .calculate_handle_gas( + None, + program_id, + allow_create_collection_payload.encode(), + 0, + true, + ) + .await + .expect("Error calculate gas"); + + let (message_id, _) = api + .send_message( + program_id, + allow_create_collection_payload, + gas_info.min_limit, + 0, + ) + .await + .expect("Error send message"); + Ok((message_id, program_id)) } @@ -137,6 +185,7 @@ pub async fn add_new_collection( meta_link: String::from("My Meta"), type_name: String::from("Simple NFT"), type_description: String::from("My Collection"), + allow_create: true }; let gas_info = api @@ -171,7 +220,7 @@ pub async fn create_collection( limit_copies: Some(1), }; let number_of_image = 10_000; - let fee = number_of_image * 257_142_857_100; + let fee = number_of_image * 282_857_142_900; let img_links_and_data: Vec<(String, ImageData)> = (0..number_of_image) .map(|i| (format!("Img-{}", i), img_data.clone())) .collect(); From 6ee5f6a7874c37c988b090f38696cf5de329235b Mon Sep 17 00:00:00 2001 From: MedovTimur Date: Wed, 3 Apr 2024 10:53:27 +0300 Subject: [PATCH 7/8] update --- io/src/lib.rs | 4 +- music-nft/io/src/lib.rs | 5 +- music-nft/src/lib.rs | 40 ++++++++---- nft/io/src/lib.rs | 3 +- nft/src/lib.rs | 28 ++++---- src/auction.rs | 26 ++++++-- src/lib.rs | 113 +++++++++++++++++---------------- src/nft_messages.rs | 1 - src/offer.rs | 6 +- src/payment.rs | 22 +++++-- tests/marketplace_node_test.rs | 61 +++++++----------- tests/test_marketplace.rs | 6 +- tests/test_music_nft.rs | 77 ++++++++++++---------- tests/test_nft.rs | 16 ++--- tests/utils_gclient/mod.rs | 1 + 15 files changed, 222 insertions(+), 187 deletions(-) diff --git a/io/src/lib.rs b/io/src/lib.rs index 6976190..5e2e9c5 100644 --- a/io/src/lib.rs +++ b/io/src/lib.rs @@ -234,7 +234,7 @@ pub enum NftMarketplaceEvent { }, ValueSent, AllowMessageChanged, - AllowCreateCollectionChanged + AllowCreateCollectionChanged, } #[derive(Debug, Clone, Encode, Decode, TypeInfo)] @@ -261,7 +261,7 @@ pub enum NftMarketplaceError { WrongValue, MintError, InsufficientBalance, - LessThanMinimumValueForTrade + LessThanMinimumValueForTrade, } #[derive(Encode, Decode, TypeInfo)] diff --git a/music-nft/io/src/lib.rs b/music-nft/io/src/lib.rs index 74003df..47d9253 100644 --- a/music-nft/io/src/lib.rs +++ b/music-nft/io/src/lib.rs @@ -110,7 +110,6 @@ pub enum MusicNftAction { RemoveAdmin { admin: ActorId, }, - } #[derive(Debug, Clone, Encode, Decode, TypeInfo)] @@ -167,7 +166,7 @@ pub enum MusicNftEvent { }, AdminRemoved { admin: ActorId, - } + }, } #[derive(Debug, Clone, Encode, Decode, TypeInfo)] @@ -189,7 +188,7 @@ pub enum MusicNftError { ThereIsNoSuchUser, ExhaustedLimit, WrongValue, - OnlyOneAdminLeft + OnlyOneAdminLeft, } #[derive(Debug, Clone, Encode, Decode, TypeInfo)] diff --git a/music-nft/src/lib.rs b/music-nft/src/lib.rs index 676ce7c..3385e6f 100644 --- a/music-nft/src/lib.rs +++ b/music-nft/src/lib.rs @@ -98,7 +98,10 @@ impl NftContract { msg::send_with_gas( minter, - Ok::(MusicNftEvent::Minted { token_id, links_and_data }), + Ok::(MusicNftEvent::Minted { + token_id, + links_and_data, + }), 0, 0, ) @@ -195,7 +198,10 @@ impl NftContract { payment_for_mint: self.config.payment_for_mint, }) } - fn expand(&mut self, additional_links: Vec<(Links, ImageData)>) -> Result { + fn expand( + &mut self, + additional_links: Vec<(Links, ImageData)>, + ) -> Result { let msg_src = msg::source(); self.check_admin(msg_src)?; if additional_links @@ -258,7 +264,7 @@ impl NftContract { } else { return Err(MusicNftError::UserRestrictionCannotBeChanged); } - + Ok(MusicNftEvent::UsersForMintAdded { users }) } fn delete_user_for_mint(&mut self, user: ActorId) -> Result { @@ -296,7 +302,7 @@ impl NftContract { let msg_src = msg::source(); self.check_admin(msg_src)?; self.admins.insert(admin); - Ok(MusicNftEvent::AdminAdded { admin}) + Ok(MusicNftEvent::AdminAdded { admin }) } fn remove_admin(&mut self, admin: ActorId) -> Result { @@ -306,7 +312,7 @@ impl NftContract { return Err(MusicNftError::OnlyOneAdminLeft); } self.admins.remove(&admin); - Ok(MusicNftEvent::AdminRemoved { admin}) + Ok(MusicNftEvent::AdminRemoved { admin }) } fn can_delete(&self) -> Result { @@ -342,7 +348,7 @@ impl NftContract { } Ok(()) } - + // Checking for sufficient mint value and sending the value to the creator fn payment_for_mint(&self, msg_value: u128) -> Result<(), MusicNftError> { if self.config.payment_for_mint != 0 { @@ -385,7 +391,12 @@ impl NftContract { } // doing all the checks to verify that the transfer can be made - fn can_transfer(&self, from: &ActorId, to: &ActorId, token_id: &NftId) -> Result<(), MusicNftError> { + fn can_transfer( + &self, + from: &ActorId, + to: &ActorId, + token_id: &NftId, + ) -> Result<(), MusicNftError> { let nft = self .tokens .get(token_id) @@ -400,7 +411,10 @@ impl NftContract { if owner != msg_src { self.check_approve(&msg_src, token_id)?; } - let time = self.config.transferable.ok_or(MusicNftError::NotTransferable)?; + let time = self + .config + .transferable + .ok_or(MusicNftError::NotTransferable)?; if exec::block_timestamp() < nft.mint_time + time { return Err(MusicNftError::NotTransferable); } @@ -418,7 +432,7 @@ extern "C" fn init() { collection_owner, config, links_and_data, - mut permission_to_mint + mut permission_to_mint, } = msg::load().expect("Unable to decode `MusicNftInit`."); let msg_value = msg::value(); @@ -533,7 +547,7 @@ extern "C" fn handle() { } else { nft_contract.mint(minter, msg_value) }; - + if result.is_err() { msg::send_with_gas(msg_source, "", 0, msg_value).expect("Error in sending value"); } @@ -613,9 +627,7 @@ impl From for NftState { .map(|(nft_id, actor_id)| (nft_id, actor_id)) .collect(); - let admins = admins - .into_iter() - .collect(); + let admins = admins.into_iter().collect(); Self { tokens, @@ -628,7 +640,7 @@ impl From for NftState { total_number_of_tokens, permission_to_mint, marketplace_address, - admins + admins, } } } diff --git a/nft/io/src/lib.rs b/nft/io/src/lib.rs index 699a5d5..7ed8fc8 100644 --- a/nft/io/src/lib.rs +++ b/nft/io/src/lib.rs @@ -157,7 +157,8 @@ pub enum NftEvent { }, AdminRemoved { admin: ActorId, - } + }, + ValueSent } #[derive(Debug, Clone, Encode, Decode, TypeInfo)] pub enum NftError { diff --git a/nft/src/lib.rs b/nft/src/lib.rs index 73616c9..7e73481 100644 --- a/nft/src/lib.rs +++ b/nft/src/lib.rs @@ -25,20 +25,22 @@ struct NftContract { static mut NFT_CONTRACT: Option = None; impl NftContract { - fn mint(&mut self, minter: ActorId, msg_value: u128) -> Result { + fn mint(&mut self, minter: ActorId, msg_value: u128, source_maketplace: bool) -> Result { // check if there are tokens for mint self.check_available_amount_of_tokens()?; - // check if a user can make a mint: - // - quantity limit - // - user-specific limit - self.check_mint(&minter)?; + if source_maketplace { + // check if a user can make a mint: + // - quantity limit + // - user-specific limit + self.check_mint(&minter)?; + // value check on mint + self.payment_for_mint(msg_value)?; + } let Some(next_nft_nonce) = self.nonce.checked_add(1) else { return Err(NftError::MathOverflow); }; - // value check on mint - self.payment_for_mint(msg_value)?; let rand_index = get_random_value(self.img_links_and_data.len() as u64); let token_id = self.nonce; @@ -340,7 +342,7 @@ impl NftContract { return Err(NftError::WrongValue); } // use send_with_gas to transfer the value directly to the balance, not to the mailbox. - msg::send_with_gas(self.collection_owner, "", 0, self.config.payment_for_mint) + msg::send_with_gas(self.collection_owner, Ok::(NftEvent::ValueSent), 0, self.config.payment_for_mint) .expect("Error in sending value"); } @@ -522,14 +524,16 @@ extern "C" fn handle() { NftAction::Mint { minter } => { let msg_source = msg::source(); let msg_value = msg::value(); - let result = if msg_source != nft_contract.marketplace_address { - Err(NftError::AccessDenied) + let result = if msg_source == nft_contract.marketplace_address { + nft_contract.mint(minter, msg_value, true) + } else if nft_contract.admins.contains(&msg_source){ + nft_contract.mint(minter, msg_value, false) } else { - nft_contract.mint(minter, msg_value) + Err(NftError::AccessDenied) }; if result.is_err() { - msg::send_with_gas(msg_source, "", 0, msg_value).expect("Error in sending value"); + msg::send_with_gas(msg_source, Ok::(NftEvent::ValueSent), 0, msg_value).expect("Error in sending value"); } result } diff --git a/src/auction.rs b/src/auction.rs index 59de268..8751048 100644 --- a/src/auction.rs +++ b/src/auction.rs @@ -76,7 +76,7 @@ impl NftMarketplace { }, self.config.gas_for_close_auction, 0, - duration+1, + duration + 1, ) .expect("Error in sending delayed message"); @@ -84,7 +84,7 @@ impl NftMarketplace { collection_address, token_id, min_price, - duration_ms: (duration+1) * self.config.ms_in_block, + duration_ms: (duration + 1) * self.config.ms_in_block, }) } @@ -100,8 +100,13 @@ impl NftMarketplace { if auction.current_winner != ActorId::zero() { // use send_with_gas with gas_limit = 0 to transfer the value directly to the balance, not to the mailbox. - msg::send_with_gas(auction.current_winner, NftMarketplaceEvent::ValueSent, 0, auction.current_price) - .expect("Error in sending value"); + msg::send_with_gas( + auction.current_winner, + Ok::(NftMarketplaceEvent::ValueSent), + 0, + auction.current_price, + ) + .expect("Error in sending value"); } auction.current_winner = msg_src; auction.current_price = msg_value; @@ -207,8 +212,13 @@ impl NftMarketplace { if auction.current_winner != ActorId::zero() { // use send_with_gas to transfer the value directly to the balance, not to the mailbox. - msg::send_with_gas(auction.current_winner, NftMarketplaceEvent::ValueSent, 0, auction.current_price) - .expect("Error in sending value"); + msg::send_with_gas( + auction.current_winner, + Ok::(NftMarketplaceEvent::ValueSent), + 0, + auction.current_price, + ) + .expect("Error in sending value"); } self.auctions @@ -234,7 +244,9 @@ impl NftMarketplace { } // if the first bid, it may be equal to the `current_price` (initial bid) - if auction.current_winner != ActorId::zero() && *bid <= auction.current_price || auction.current_winner == ActorId::zero() && *bid < auction.current_price { + if auction.current_winner != ActorId::zero() && *bid <= auction.current_price + || auction.current_winner == ActorId::zero() && *bid < auction.current_price + { return Err(NftMarketplaceError::LessOrEqualThanBid); } auction diff --git a/src/lib.rs b/src/lib.rs index 9398825..c3502db 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,7 @@ #![no_std] use crate::nft_messages::*; -use gstd::{ - collections::HashMap, exec, msg, prelude::*, prog::ProgramGenerator, ActorId, CodeId -}; +use gstd::{collections::HashMap, exec, msg, prelude::*, prog::ProgramGenerator, ActorId, CodeId}; use nft_marketplace_io::*; mod auction; @@ -52,7 +50,8 @@ extern "C" fn init() { max_number_of_images, } = msg::load().expect("Unable to decode `NftMarketplaceInit`"); let existential_deposit = exec::env_vars().existential_deposit; - let minimum_value_for_trade = existential_deposit * 10_000 / (10_000 - royalty_to_marketplace_for_trade) as u128; + let minimum_value_for_trade = + existential_deposit * 10_000 / (10_000 - royalty_to_marketplace_for_trade) as u128; let nft_marketplace = NftMarketplace { admins: vec![msg::source()], config: Config { @@ -68,7 +67,7 @@ extern "C" fn init() { ms_in_block, fee_per_uploaded_file, max_creator_royalty, - max_number_of_images + max_number_of_images, }, minimum_value_for_trade, ..Default::default() @@ -82,7 +81,7 @@ extern "C" fn init() { minimum_value_for_trade, fee_per_uploaded_file, max_creator_royalty, - max_number_of_images + max_number_of_images, }), 0, ) @@ -106,22 +105,34 @@ async fn main() { type_name, type_description, allow_create, - } => nft_marketplace.add_new_collection(code_id, meta_link, type_name, type_description, allow_create), + } => nft_marketplace.add_new_collection( + code_id, + meta_link, + type_name, + type_description, + allow_create, + ), NftMarketplaceAction::CreateCollection { type_name, payload } => { let msg_source = msg::source(); let msg_value = msg::value(); - let reply = nft_marketplace.create_collection(type_name, payload, msg_source, msg_value).await; + let reply = nft_marketplace + .create_collection(type_name, payload, msg_source, msg_value) + .await; if reply.is_err() { - msg::send_with_gas(msg_source, NftMarketplaceEvent::ValueSent, 0, msg_value).expect("Error in sending value"); + msg::send_with_gas(msg_source, Ok::(NftMarketplaceEvent::ValueSent), 0, msg_value) + .expect("Error in sending value"); } reply } NftMarketplaceAction::Mint { collection_address } => { let msg_source = msg::source(); let msg_value = msg::value(); - let reply = nft_marketplace.mint(collection_address, msg_source, msg_value).await; + let reply = nft_marketplace + .mint(collection_address, msg_source, msg_value) + .await; if reply.is_err() { - msg::send_with_gas(msg_source, NftMarketplaceEvent::ValueSent, 0, msg_value).expect("Error in sending value"); + msg::send_with_gas(msg_source, Ok::(NftMarketplaceEvent::ValueSent), 0, msg_value) + .expect("Error in sending value"); } reply } @@ -152,7 +163,8 @@ async fn main() { .buy(collection_address, token_id, msg_source, msg_value) .await; if reply.is_err() { - msg::send_with_gas(msg_source, NftMarketplaceEvent::ValueSent, 0, msg_value).expect("Error in sending value"); + msg::send_with_gas(msg_source, Ok::(NftMarketplaceEvent::ValueSent), 0, msg_value) + .expect("Error in sending value"); } reply } @@ -172,12 +184,13 @@ async fn main() { } => { let msg_source = msg::source(); let msg_value = msg::value(); - let reply = nft_marketplace.add_bid(collection_address, token_id, msg_source, msg_value); + let reply = + nft_marketplace.add_bid(collection_address, token_id, msg_source, msg_value); if reply.is_err() { - msg::send_with_gas(msg_source, NftMarketplaceEvent::ValueSent, 0, msg_value).expect("Error in sending value"); + msg::send_with_gas(msg_source, Ok::(NftMarketplaceEvent::ValueSent), 0, msg_value) + .expect("Error in sending value"); } reply - } NftMarketplaceAction::CloseAuction { collection_address, @@ -206,7 +219,8 @@ async fn main() { .create_offer(collection_address, token_id, msg_source, msg_value) .await; if reply.is_err() { - msg::send_with_gas(msg_source, NftMarketplaceEvent::ValueSent, 0, msg_value).expect("Error in sending value"); + msg::send_with_gas(msg_source, Ok::(NftMarketplaceEvent::ValueSent), 0, msg_value) + .expect("Error in sending value"); } reply } @@ -233,7 +247,7 @@ async fn main() { ms_in_block, fee_per_uploaded_file, max_creator_royalty, - max_number_of_images + max_number_of_images, } => nft_marketplace.update_config( gas_for_creation, gas_for_mint, @@ -247,11 +261,16 @@ async fn main() { ms_in_block, fee_per_uploaded_file, max_creator_royalty, - max_number_of_images + max_number_of_images, ), NftMarketplaceAction::AllowMessage(allow) => nft_marketplace.allow_message(allow), - NftMarketplaceAction::AllowCreateCollection(allow) => nft_marketplace.allow_create_collection(allow), - NftMarketplaceAction::AddExternalCollection { collection_address, type_name } => nft_marketplace.add_external_collection(collection_address, type_name), + NftMarketplaceAction::AllowCreateCollection(allow) => { + nft_marketplace.allow_create_collection(allow) + } + NftMarketplaceAction::AddExternalCollection { + collection_address, + type_name, + } => nft_marketplace.add_external_collection(collection_address, type_name), }; msg::reply(result, 0).expect( @@ -295,7 +314,6 @@ impl NftMarketplace { msg_src: ActorId, msg_value: u128, ) -> Result { - self.check_time_creation(&msg_src)?; self.check_allow_create_collection(&msg_src)?; self.check_allow_message(&msg_src)?; @@ -340,7 +358,7 @@ impl NftMarketplace { &mut self, collection_address: ActorId, msg_src: ActorId, - msg_value: u128 + msg_value: u128, ) -> Result { self.check_allow_message(&msg_src)?; mint( @@ -455,7 +473,8 @@ impl NftMarketplace { let minimum_value_for_trade = if let Some(royalty) = royalty_to_marketplace_for_trade { self.config.royalty_to_marketplace_for_trade = royalty; let existential_deposit = exec::env_vars().existential_deposit; - self.minimum_value_for_trade = existential_deposit * 10_000 / (10_000 - royalty) as u128; + self.minimum_value_for_trade = + existential_deposit * 10_000 / (10_000 - royalty) as u128; Some(self.minimum_value_for_trade) } else { None @@ -490,7 +509,7 @@ impl NftMarketplace { minimum_value_for_trade, fee_per_uploaded_file, max_creator_royalty, - max_number_of_images + max_number_of_images, }) } pub fn allow_message( @@ -501,7 +520,6 @@ impl NftMarketplace { self.check_admin(&msg_src)?; self.allow_message = allow; Ok(NftMarketplaceEvent::AllowMessageChanged) - } pub fn allow_create_collection( &mut self, @@ -511,7 +529,6 @@ impl NftMarketplace { self.check_admin(&msg_src)?; self.allow_create_collection = allow; Ok(NftMarketplaceEvent::AllowCreateCollectionChanged) - } pub fn add_external_collection( @@ -521,10 +538,11 @@ impl NftMarketplace { ) -> Result { let msg_src = msg::source(); self.check_admin(&msg_src)?; - if !self.type_collections.contains_key(&type_name){ + if !self.type_collections.contains_key(&type_name) { return Err(NftMarketplaceError::WrongCollectionName); } - self.collection_to_owner.insert(collection_address, (type_name.clone(), msg_src)); + self.collection_to_owner + .insert(collection_address, (type_name.clone(), msg_src)); Ok(NftMarketplaceEvent::CollectionCreated { type_name, collection_address, @@ -536,11 +554,12 @@ impl NftMarketplace { self.check_admin(&msg_src)?; let balance = exec::value_available(); let existential_deposit = exec::env_vars().existential_deposit; - if balance < 2*existential_deposit { + if balance < 2 * existential_deposit { return Err(NftMarketplaceError::InsufficientBalance); } let value = balance - existential_deposit; - msg::send_with_gas(msg_src, NftMarketplaceEvent::ValueSent, 0, value).expect("Error in sending value"); + msg::send_with_gas(msg_src, Ok::(NftMarketplaceEvent::ValueSent), 0, value) + .expect("Error in sending value"); Ok(NftMarketplaceEvent::BalanceHasBeenWithdrawn { value }) } @@ -607,18 +626,12 @@ extern "C" fn state() { StateQuery::All => StateReply::All(nft_marketplace.into()), StateQuery::Admins => StateReply::Admins(nft_marketplace.admins), StateQuery::CollectionsInfo => { - let type_collections = nft_marketplace - .type_collections - .into_iter() - .collect(); + let type_collections = nft_marketplace.type_collections.into_iter().collect(); StateReply::CollectionsInfo(type_collections) } StateQuery::Config => StateReply::Config(nft_marketplace.config), StateQuery::AllCollections => { - let collection_to_owner = nft_marketplace - .collection_to_owner - .into_iter() - .collect(); + let collection_to_owner = nft_marketplace.collection_to_owner.into_iter().collect(); StateReply::AllCollections(collection_to_owner) } StateQuery::GetCollectionInfo(collection_address) => { @@ -656,29 +669,19 @@ impl From for State { config, minimum_value_for_trade, allow_message, - allow_create_collection + allow_create_collection, } = value; - let collection_to_owner = collection_to_owner - .into_iter() - .collect(); + let collection_to_owner = collection_to_owner.into_iter().collect(); - let time_creation = time_creation - .into_iter() - .collect(); + let time_creation = time_creation.into_iter().collect(); - let type_collections = type_collections - .into_iter() - .collect(); + let type_collections = type_collections.into_iter().collect(); let sales = sales.into_iter().collect(); - let auctions = auctions - .into_iter() - .collect(); + let auctions = auctions.into_iter().collect(); - let offers = offers - .into_iter() - .collect(); + let offers = offers.into_iter().collect(); Self { admins, @@ -691,7 +694,7 @@ impl From for State { config, minimum_value_for_trade, allow_message, - allow_create_collection + allow_create_collection, } } } diff --git a/src/nft_messages.rs b/src/nft_messages.rs index 2d97567..41051eb 100644 --- a/src/nft_messages.rs +++ b/src/nft_messages.rs @@ -9,7 +9,6 @@ pub async fn mint( gas_for_get_info: u64, royalty_to_marketplace: u16, ) -> Result { - let get_payment_for_mint_payload = NftAction::GetPaymentForMint; let reply = msg::send_with_gas_for_reply_as::>( collection_address, diff --git a/src/offer.rs b/src/offer.rs index 2e9dadf..56b9deb 100644 --- a/src/offer.rs +++ b/src/offer.rs @@ -60,7 +60,8 @@ impl NftMarketplace { self.offers .entry(offer.clone()) .and_modify(|price| { - msg::send_with_gas(offer.creator, NftMarketplaceEvent::ValueSent, 0, *price).expect("Error in sending value"); + msg::send_with_gas(offer.creator, Ok::(NftMarketplaceEvent::ValueSent), 0, *price) + .expect("Error in sending value"); *price = msg_value; }) .or_insert(msg_value); @@ -87,7 +88,8 @@ impl NftMarketplace { if let Some((offer, price)) = self.offers.get_key_value(&offer) { // use send_with_gas to transfer the value directly to the balance, not to the mailbox. - msg::send_with_gas(offer.creator, NftMarketplaceEvent::ValueSent, 0, *price).expect("Error in sending value"); + msg::send_with_gas(offer.creator, Ok::(NftMarketplaceEvent::ValueSent), 0, *price) + .expect("Error in sending value"); } else { return Err(NftMarketplaceError::WrongDataOffer); } diff --git a/src/payment.rs b/src/payment.rs index 0f6de4c..a5ef4e7 100644 --- a/src/payment.rs +++ b/src/payment.rs @@ -1,6 +1,6 @@ use gcore::exec; use gstd::{msg, ActorId}; -use nft_marketplace_io::NftMarketplaceEvent; +use nft_marketplace_io::{NftMarketplaceEvent, NftMarketplaceError}; pub fn currency_transfer( collection_owner: ActorId, @@ -15,17 +15,27 @@ pub fn currency_transfer( let percent_to_marketplace = price * (royalty_to_marketplace as u128) / 10_000u128; if percent_to_collection_creator > exec::env_vars().existential_deposit { // use send_with_gas to transfer the value directly to the balance, not to the mailbox. - msg::send_with_gas(collection_owner, NftMarketplaceEvent::ValueSent, 0, percent_to_collection_creator) - .expect("Error in sending value"); + msg::send_with_gas( + collection_owner, + Ok::(NftMarketplaceEvent::ValueSent), + 0, + percent_to_collection_creator, + ) + .expect("Error in sending value"); msg::send_with_gas( token_owner, - NftMarketplaceEvent::ValueSent, + Ok::(NftMarketplaceEvent::ValueSent), 0, price - percent_to_collection_creator - percent_to_marketplace, ) .expect("Error in sending value"); } else { - msg::send_with_gas(token_owner, NftMarketplaceEvent::ValueSent, 0, price - percent_to_marketplace) - .expect("Error in sending value"); + msg::send_with_gas( + token_owner, + Ok::(NftMarketplaceEvent::ValueSent), + 0, + price - percent_to_marketplace, + ) + .expect("Error in sending value"); } } diff --git a/tests/marketplace_node_test.rs b/tests/marketplace_node_test.rs index f1b9a21..b9df316 100644 --- a/tests/marketplace_node_test.rs +++ b/tests/marketplace_node_test.rs @@ -1,8 +1,8 @@ use gclient::{EventProcessor, GearApi, Result}; use gear_core::ids::ProgramId; use gstd::{prelude::*, ActorId}; -use nft_marketplace_io::*; use nft_io::{Config as NftConfig, ImageData, NftInit}; +use nft_marketplace_io::*; mod utils_gclient; use utils_gclient::*; @@ -52,27 +52,23 @@ async fn create_test() -> Result<()> { assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); let percent_to_marketplace = 10_000_000_000_000 * 200 as u128 / 10_000; - let payment_for_mint = 10_000_000_000_000 + percent_to_marketplace ; + let payment_for_mint = 10_000_000_000_000 + percent_to_marketplace; let message_id = mint(&api, program_id, address_nft, payment_for_mint) .await .expect("Error mint"); assert!(listener.message_processed(message_id).await?.succeed()); - // Check marketplace and collection owner balance + // Check marketplace and collection owner balance let marketplace_balance_after_mint = api.total_balance(program_id).await?; assert_eq!( marketplace_balance_after_mint, marketplace_balance + percent_to_marketplace, "Wrong value" ); - + let collection_owner_balance = api.total_balance(get_program_id_from_u64(USERS[0])).await?; - assert_eq!( - collection_owner_balance, - 10_000_000_000_000, - "Wrong value" - ); + assert_eq!(collection_owner_balance, 10_000_000_000_000, "Wrong value"); // Check success of mint let state = get_all_state_nft(&api, &nft_pid) @@ -81,7 +77,7 @@ async fn create_test() -> Result<()> { assert!(!state.tokens.is_empty()); // assert_eq!(state.img_links_and_data.len(), 9); - + assert_eq!(state.img_links_and_data.len(), 9999); Ok(()) } @@ -133,13 +129,13 @@ async fn sale_test() -> Result<()> { assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); let percent_to_marketplace = 10_000_000_000_000 * 200 as u128 / 10_000; - let payment_for_mint = 10_000_000_000_000 + percent_to_marketplace ; + let payment_for_mint = 10_000_000_000_000 + percent_to_marketplace; let message_id = mint(&api, program_id, address_nft, payment_for_mint) .await .expect("Error mint"); assert!(listener.message_processed(message_id).await?.succeed()); - // Check marketplace and collection owner balance + // Check marketplace and collection owner balance let marketplace_balance_after_mint = api.total_balance(program_id).await?; assert_eq!( marketplace_balance_after_mint, @@ -147,11 +143,7 @@ async fn sale_test() -> Result<()> { "Wrong value" ); let collection_owner_balance = api.total_balance(get_program_id_from_u64(USERS[0])).await?; - assert_eq!( - collection_owner_balance, - 10_000_000_000_000, - "Wrong value" - ); + assert_eq!(collection_owner_balance, 10_000_000_000_000, "Wrong value"); // Check success of mint let state = get_all_state_nft(&api, &nft_pid) @@ -232,7 +224,7 @@ async fn sale_test() -> Result<()> { let collection_owner_balance = api.total_balance(get_program_id_from_u64(USERS[0])).await?; assert_eq!( collection_owner_balance, - 10_000_000_000_000+percent_to_collection_owner, + 10_000_000_000_000 + percent_to_collection_owner, "Wrong value" ); @@ -286,13 +278,13 @@ async fn auction_test() -> Result<()> { assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); let percent_to_marketplace = 10_000_000_000_000 * 200 as u128 / 10_000; - let payment_for_mint = 10_000_000_000_000 + percent_to_marketplace ; + let payment_for_mint = 10_000_000_000_000 + percent_to_marketplace; let message_id = mint(&api, program_id, address_nft, payment_for_mint) .await .expect("Error mint"); assert!(listener.message_processed(message_id).await?.succeed()); - // Check marketplace and collection owner balance + // Check marketplace and collection owner balance let marketplace_balance_after_mint = api.total_balance(program_id).await?; assert_eq!( marketplace_balance_after_mint, @@ -300,11 +292,7 @@ async fn auction_test() -> Result<()> { "Wrong value" ); let collection_owner_balance = api.total_balance(get_program_id_from_u64(USERS[0])).await?; - assert_eq!( - collection_owner_balance, - 10_000_000_000_000, - "Wrong value" - ); + assert_eq!(collection_owner_balance, 10_000_000_000_000, "Wrong value"); // Check success of mint let state = get_all_state_nft(&api, &nft_pid) @@ -404,7 +392,10 @@ async fn auction_test() -> Result<()> { let state = get_marketplace_state(&api, &program_id) .await .expect("Unexpected invalid state."); - assert_eq!(state.auctions[0].1.current_winner, client_2.get_specific_actor_id(USERS_STR[1])); + assert_eq!( + state.auctions[0].1.current_winner, + client_2.get_specific_actor_id(USERS_STR[1]) + ); let balance_user_0_new = api.total_balance(user_0).await?; assert_eq!( @@ -433,7 +424,7 @@ async fn auction_test() -> Result<()> { assert_eq!(token.0, 0); assert_eq!(token.1.owner, api.get_specific_actor_id(USERS_STR[1])); - // Check marketplace and collection owner balance + // Check marketplace and collection owner balance let marketplace_balance_after_auction = api.total_balance(program_id).await?; assert_eq!( marketplace_balance_after_auction, @@ -443,7 +434,7 @@ async fn auction_test() -> Result<()> { let collection_owner_balance = api.total_balance(get_program_id_from_u64(USERS[1])).await?; assert_eq!( collection_owner_balance, - 10_000_000_000_000+percent_to_collection_owner, + 10_000_000_000_000 + percent_to_collection_owner, "Wrong value" ); Ok(()) @@ -496,7 +487,7 @@ async fn offer_test() -> Result<()> { assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); let percent_to_marketplace = 10_000_000_000_000 * 200 as u128 / 10_000; - let payment_for_mint = 10_000_000_000_000 + percent_to_marketplace ; + let payment_for_mint = 10_000_000_000_000 + percent_to_marketplace; let message_id = mint(&api, program_id, address_nft, payment_for_mint) .await .expect("Error mint"); @@ -510,11 +501,7 @@ async fn offer_test() -> Result<()> { "Wrong value" ); let collection_owner_balance = api.total_balance(get_program_id_from_u64(USERS[0])).await?; - assert_eq!( - collection_owner_balance, - 10_000_000_000_000, - "Wrong value" - ); + assert_eq!(collection_owner_balance, 10_000_000_000_000, "Wrong value"); // Check success of mint let state = get_all_state_nft(&api, &nft_pid) @@ -536,7 +523,6 @@ async fn offer_test() -> Result<()> { let percent_to_marketplace = offer_price * royalty_to_marketplace as u128 / 10_000; let percent_to_collection_owner = offer_price * royalty_to_collection_owner as u128 / 10_000; - let gas_info = client .calculate_handle_gas( None, @@ -601,7 +587,7 @@ async fn offer_test() -> Result<()> { assert_eq!(token.0, 0); assert_eq!(token.1.owner, api.get_specific_actor_id(USERS_STR[0])); - // Check marketplace and collection owner balance + // Check marketplace and collection owner balance let marketplace_balance_after_offer = api.total_balance(program_id).await?; assert_eq!( marketplace_balance_after_offer, @@ -611,14 +597,13 @@ async fn offer_test() -> Result<()> { let collection_owner_balance = api.total_balance(get_program_id_from_u64(USERS[0])).await?; assert_eq!( collection_owner_balance, - 10_000_000_000_000+percent_to_collection_owner, + 10_000_000_000_000 + percent_to_collection_owner, "Wrong value" ); Ok(()) } - // #[tokio::test] // #[ignore] // async fn nft_init() -> Result<()> { diff --git a/tests/test_marketplace.rs b/tests/test_marketplace.rs index 78ada2d..2dabe27 100644 --- a/tests/test_marketplace.rs +++ b/tests/test_marketplace.rs @@ -658,7 +658,7 @@ fn auction_success() { println!("STATE: {:?}", state); } - sys.spend_blocks(duration+1); + sys.spend_blocks(duration + 1); let percent_to_collection_owner = final_bid * royalty as u128 / 10_000; let percent_to_marketplace = final_bid * 200 as u128 / 10_000; @@ -840,7 +840,7 @@ fn auction_cancel() { assert!(!state.auctions.is_empty()); } - sys.spend_blocks(duration_1+1); + sys.spend_blocks(duration_1 + 1); // the delayed message from the first version of the auction will come, // but it should end with an error, because the auction was canceled and a new one was created. @@ -1010,7 +1010,7 @@ fn auction_failures() { let balance = sys.balance_of(USERS[2]); assert_eq!(balance, 20_000_000_000_000, "Wrong balance"); - sys.spend_blocks(duration+1); + sys.spend_blocks(duration + 1); sys.mint_to(USERS[3], 15_000_000_000_000); marketplace.add_bid( USERS[3], diff --git a/tests/test_music_nft.rs b/tests/test_music_nft.rs index 443bf5d..b637006 100644 --- a/tests/test_music_nft.rs +++ b/tests/test_music_nft.rs @@ -2,8 +2,11 @@ use crate::utils::*; use utils::prelude::*; mod utils; use gtest::Program; +use music_nft_io::{ + Config, ImageData, Links, ListenCapability, MusicNftAction, MusicNftError, NftState, + StateQuery as StateQueryNft, StateReply as StateReplyNft, +}; use nft_marketplace_io::*; -use music_nft_io::{Config, ImageData, Links, ListenCapability, MusicNftAction, MusicNftError, StateQuery as StateQueryNft, StateReply as StateReplyNft, NftState}; const USERS: &[u64] = &[5, 6, 7, 8]; #[test] @@ -82,7 +85,7 @@ fn successful_basics() { payment_for_mint: 0, transferable: Some(0), sellable: Some(0), - listening_capabilities: ListenCapability::Demo + listening_capabilities: ListenCapability::Demo, }; let res = nft_collection.send(USERS[0], MusicNftAction::ChangeConfig { config }); assert!(!res.main_failed()); @@ -180,23 +183,25 @@ fn successful_basics() { // Successful Expand NFT in the collection let img_data = ImageData { limit_copies: Some(1), - description: None + description: None, }; - let additional_links = vec![(Links{ - img_link: None, - music_link: "add_link_1".to_string() - }, img_data.clone()), - (Links{ - img_link: None, - music_link: "add_link_2".to_string() - }, img_data.clone()), + let additional_links = vec![ + ( + Links { + img_link: None, + music_link: "add_link_1".to_string(), + }, + img_data.clone(), + ), + ( + Links { + img_link: None, + music_link: "add_link_2".to_string(), + }, + img_data.clone(), + ), ]; - let res = nft_collection.send( - USERS[0], - MusicNftAction::Expand { - additional_links, - }, - ); + let res = nft_collection.send(USERS[0], MusicNftAction::Expand { additional_links }); assert!(!res.main_failed()); let state_reply = nft_collection .read_state(StateQueryNft::All) @@ -227,7 +232,8 @@ fn failures() { ); // The mint limit must be greater than zero - let mut init_music_nft_payload = get_init_music_nft_payload(USERS[0].into(), 0, Some(0), 0, None); + let mut init_music_nft_payload = + get_init_music_nft_payload(USERS[0].into(), 0, Some(0), 0, None); sys.mint_to(USERS[0], 100_000_000_000_000); marketplace.create_collection( USERS[0], @@ -258,11 +264,11 @@ fn failures() { // Limit of copies value is equal to 0 let img_data = ImageData { limit_copies: Some(0), - description: None + description: None, }; - let links = Links{ + let links = Links { img_link: None, - music_link: "Music link-0".to_owned() + music_link: "Music link-0".to_owned(), }; init_music_nft_payload.links_and_data = vec![(links, img_data.clone())]; marketplace.create_collection( @@ -341,7 +347,7 @@ fn failures() { payment_for_mint: 0, transferable: Some(0), sellable: Some(0), - listening_capabilities: ListenCapability::Demo + listening_capabilities: ListenCapability::Demo, }; let res = nft_collection.send(USERS[0], MusicNftAction::ChangeConfig { config }); check_music_nft_error(USERS[0], &res, MusicNftError::ConfigCannotBeChanged); @@ -356,18 +362,16 @@ fn failures() { let img_data = ImageData { limit_copies: Some(4), - description: None + description: None, }; - let additional_links = vec![(Links{ - img_link: None, - music_link: "New_img".to_owned() - }, img_data.clone())]; - let res = nft_collection.send( - USERS[0], - MusicNftAction::Expand { - additional_links, + let additional_links = vec![( + Links { + img_link: None, + music_link: "New_img".to_owned(), }, - ); + img_data.clone(), + )]; + let res = nft_collection.send(USERS[0], MusicNftAction::Expand { additional_links }); assert!(!res.main_failed()); marketplace.mint(USERS[2], address_nft, None); @@ -427,7 +431,8 @@ fn check_transferable() { None, ); - let mut init_music_nft_payload = get_init_music_nft_payload(USERS[0].into(), 0, Some(3), 0, None); + let mut init_music_nft_payload = + get_init_music_nft_payload(USERS[0].into(), 0, Some(3), 0, None); let transferable_time = 9_000; init_music_nft_payload.config.transferable = Some(transferable_time); sys.mint_to(USERS[0], 100_000_000_000_000); @@ -527,7 +532,8 @@ fn check_payment_for_mint() { } // The payment for mint must be greater than existential deposit (10000000000000) - let mut init_music_nft_payload = get_init_music_nft_payload(USERS[0].into(), 0, Some(3), 0, None); + let mut init_music_nft_payload = + get_init_music_nft_payload(USERS[0].into(), 0, Some(3), 0, None); let payment_for_mint = 9_000_000_000_000; init_music_nft_payload.config.payment_for_mint = payment_for_mint; sys.mint_to(USERS[0], 100_000_000_000_000); @@ -641,7 +647,8 @@ fn permission_to_mint() { println!("Collection info: {:?}", state); } // Successful creation of a new collection - let init_music_nft_payload = get_init_music_nft_payload(USERS[0].into(), 0, Some(3), 0, Some(vec![])); + let init_music_nft_payload = + get_init_music_nft_payload(USERS[0].into(), 0, Some(3), 0, Some(vec![])); sys.mint_to(USERS[0], 100_000_000_000_000); marketplace.create_collection( USERS[0], diff --git a/tests/test_nft.rs b/tests/test_nft.rs index b1e4122..4f17bd7 100644 --- a/tests/test_nft.rs +++ b/tests/test_nft.rs @@ -712,11 +712,11 @@ fn get_state(nft_collection: &Program) -> Option { // } -// #[test] -// fn check() { -// let input = "000080ee3600000000008c9de8de3b0000000000000000000000c800c800"; -// let decoded = hex::decode(input).expect("Decoding failed"); -// let mut res: &[u8] = &decoded; -// let result = Result::::decode(&mut res).ok(); -// println!("RES: {:?}", result); -// } +#[test] +fn check() { + let input = "13"; + let decoded = hex::decode(input).expect("Decoding failed"); + let mut res: &[u8] = &decoded; + let result = Result::::decode(&mut res).ok(); + println!("RES: {:?}", result); +} diff --git a/tests/utils_gclient/mod.rs b/tests/utils_gclient/mod.rs index 44ff1b6..327506a 100644 --- a/tests/utils_gclient/mod.rs +++ b/tests/utils_gclient/mod.rs @@ -372,3 +372,4 @@ pub fn get_program_id_from_u64(u64: u64) -> ProgramId { let list: [u8; 32] = actor_id.into(); list.into() } + From e94a85eef37524a980ffcc372752a118ba332881 Mon Sep 17 00:00:00 2001 From: MedovTimur Date: Fri, 10 May 2024 18:09:31 +0300 Subject: [PATCH 8/8] some changes --- nft/io/src/lib.rs | 35 ++++++- nft/src/lib.rs | 111 ++++++++++++++++++---- src/lib.rs | 54 ++++++++--- src/offer.rs | 18 +++- src/payment.rs | 2 +- tests/test_nft.rs | 183 ++++++++++++++++++++++++++++++++++++- tests/utils/mod.rs | 17 ++-- tests/utils_gclient/mod.rs | 42 +++------ 8 files changed, 385 insertions(+), 77 deletions(-) diff --git a/nft/io/src/lib.rs b/nft/io/src/lib.rs index 7ed8fc8..89c6cb4 100644 --- a/nft/io/src/lib.rs +++ b/nft/io/src/lib.rs @@ -45,6 +45,7 @@ pub struct Config { pub payment_for_mint: u128, pub transferable: Option, pub sellable: Option, + pub variable_meta: bool, } #[derive(Debug, Clone, Encode, Decode, TypeInfo)] @@ -101,6 +102,21 @@ pub enum NftAction { RemoveAdmin { admin: ActorId, }, + AddMetadata { + nft_id: u64, + metadata: String, + }, + ChangeImageLink { + nft_id: u64, + img_link: String, + }, + ChangeMetadata { + nft_id: u64, + metadata: Vec, + }, + DeleteMetadata { + nft_id: u64, + }, } #[derive(Debug, Clone, Encode, Decode, TypeInfo)] @@ -158,7 +174,22 @@ pub enum NftEvent { AdminRemoved { admin: ActorId, }, - ValueSent + ValueSent, + MetadataAdded { + nft_id: u64, + metadata: String, + }, + ImageLinkChanged { + nft_id: u64, + img_link: String, + }, + MetadataChanged { + nft_id: u64, + metadata: Vec, + }, + MetadataDeleted { + nft_id: u64, + }, } #[derive(Debug, Clone, Encode, Decode, TypeInfo)] pub enum NftError { @@ -179,7 +210,7 @@ pub enum NftError { ThereIsNoSuchUser, ExhaustedLimit, WrongValue, - OnlyOneAdminLeft + OnlyOneAdminLeft, } #[derive(Debug, Clone, Encode, Decode, TypeInfo)] diff --git a/nft/src/lib.rs b/nft/src/lib.rs index 7e73481..55599de 100644 --- a/nft/src/lib.rs +++ b/nft/src/lib.rs @@ -25,7 +25,12 @@ struct NftContract { static mut NFT_CONTRACT: Option = None; impl NftContract { - fn mint(&mut self, minter: ActorId, msg_value: u128, source_maketplace: bool) -> Result { + fn mint( + &mut self, + minter: ActorId, + msg_value: u128, + source_maketplace: bool, + ) -> Result { // check if there are tokens for mint self.check_available_amount_of_tokens()?; @@ -249,7 +254,6 @@ impl NftContract { } else { return Err(NftError::UserRestrictionCannotBeChanged); } - Ok(NftEvent::UsersForMintAdded { users }) } @@ -283,12 +287,12 @@ impl NftContract { Ok(NftEvent::LiftRestrictionMint) } - + fn add_admin(&mut self, admin: ActorId) -> Result { let msg_src = msg::source(); self.check_admin(msg_src)?; self.admins.insert(admin); - Ok(NftEvent::AdminAdded { admin}) + Ok(NftEvent::AdminAdded { admin }) } fn remove_admin(&mut self, admin: ActorId) -> Result { let msg_src = msg::source(); @@ -297,7 +301,56 @@ impl NftContract { return Err(NftError::OnlyOneAdminLeft); } self.admins.remove(&admin); - Ok(NftEvent::AdminRemoved { admin}) + Ok(NftEvent::AdminRemoved { admin }) + } + + fn add_metadata(&mut self, nft_id: u64, metadata: String) -> Result { + let msg_src = msg::source(); + self.check_admin(msg_src)?; + self.check_variable_meta()?; + let nft = self + .tokens + .get_mut(&nft_id) + .ok_or(NftError::TokenDoesNotExist)?; + nft.metadata.push(metadata.clone()); + Ok(NftEvent::MetadataAdded { nft_id, metadata }) + } + fn change_img_link(&mut self, nft_id: u64, img_link: String) -> Result { + let msg_src = msg::source(); + self.check_admin(msg_src)?; + self.check_variable_meta()?; + let nft = self + .tokens + .get_mut(&nft_id) + .ok_or(NftError::TokenDoesNotExist)?; + nft.media_url = img_link.clone(); + Ok(NftEvent::ImageLinkChanged { nft_id, img_link }) + } + fn change_metadata( + &mut self, + nft_id: u64, + metadata: Vec, + ) -> Result { + let msg_src = msg::source(); + self.check_admin(msg_src)?; + self.check_variable_meta()?; + let nft = self + .tokens + .get_mut(&nft_id) + .ok_or(NftError::TokenDoesNotExist)?; + nft.metadata = metadata.clone(); + Ok(NftEvent::MetadataChanged { nft_id, metadata }) + } + fn delete_metadata(&mut self, nft_id: u64) -> Result { + let msg_src = msg::source(); + self.check_admin(msg_src)?; + self.check_variable_meta()?; + let nft = self + .tokens + .get_mut(&nft_id) + .ok_or(NftError::TokenDoesNotExist)?; + nft.metadata = vec![]; + Ok(NftEvent::MetadataDeleted { nft_id }) } fn can_delete(&self) -> Result { @@ -310,7 +363,12 @@ impl NftContract { } Ok(()) } - + fn check_variable_meta(&self) -> Result<(), NftError> { + if !self.config.variable_meta { + return Err(NftError::AccessDenied); + } + Ok(()) + } fn check_available_amount_of_tokens(&self) -> Result<(), NftError> { if self.img_links_and_data.is_empty() { return Err(NftError::AllTokensMinted); @@ -342,8 +400,13 @@ impl NftContract { return Err(NftError::WrongValue); } // use send_with_gas to transfer the value directly to the balance, not to the mailbox. - msg::send_with_gas(self.collection_owner, Ok::(NftEvent::ValueSent), 0, self.config.payment_for_mint) - .expect("Error in sending value"); + msg::send_with_gas( + self.collection_owner, + Ok::(NftEvent::ValueSent), + 0, + self.config.payment_for_mint, + ) + .expect("Error in sending value"); } Ok(()) @@ -526,14 +589,20 @@ extern "C" fn handle() { let msg_value = msg::value(); let result = if msg_source == nft_contract.marketplace_address { nft_contract.mint(minter, msg_value, true) - } else if nft_contract.admins.contains(&msg_source){ + } else if nft_contract.admins.contains(&msg_source) { nft_contract.mint(minter, msg_value, false) } else { Err(NftError::AccessDenied) }; - + if result.is_err() { - msg::send_with_gas(msg_source, Ok::(NftEvent::ValueSent), 0, msg_value).expect("Error in sending value"); + msg::send_with_gas( + msg_source, + Ok::(NftEvent::ValueSent), + 0, + msg_value, + ) + .expect("Error in sending value"); } result } @@ -555,6 +624,14 @@ extern "C" fn handle() { NftAction::LiftRestrictionMint => nft_contract.lift_restrictions_mint(), NftAction::AddAdmin { admin } => nft_contract.add_admin(admin), NftAction::RemoveAdmin { admin } => nft_contract.remove_admin(admin), + NftAction::AddMetadata { nft_id, metadata } => nft_contract.add_metadata(nft_id, metadata), + NftAction::ChangeImageLink { nft_id, img_link } => { + nft_contract.change_img_link(nft_id, img_link) + } + NftAction::ChangeMetadata { nft_id, metadata } => { + nft_contract.change_metadata(nft_id, metadata) + } + NftAction::DeleteMetadata { nft_id } => nft_contract.delete_metadata(nft_id), }; msg::reply(result, 0).expect("Failed to encode or reply with `Result`."); @@ -597,19 +674,13 @@ impl From for NftState { .. } = value; - let tokens = tokens - .into_iter() - .collect(); + let tokens = tokens.into_iter().collect(); let owners = owners .into_iter() .map(|(actor_id, token_set)| (actor_id, token_set.into_iter().collect())) .collect(); - let token_approvals = token_approvals - .into_iter() - .collect(); - let admins = admins - .into_iter() - .collect(); + let token_approvals = token_approvals.into_iter().collect(); + let admins = admins.into_iter().collect(); Self { tokens, diff --git a/src/lib.rs b/src/lib.rs index c3502db..68a6fe0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -119,8 +119,13 @@ async fn main() { .create_collection(type_name, payload, msg_source, msg_value) .await; if reply.is_err() { - msg::send_with_gas(msg_source, Ok::(NftMarketplaceEvent::ValueSent), 0, msg_value) - .expect("Error in sending value"); + msg::send_with_gas( + msg_source, + Ok::(NftMarketplaceEvent::ValueSent), + 0, + msg_value, + ) + .expect("Error in sending value"); } reply } @@ -131,8 +136,13 @@ async fn main() { .mint(collection_address, msg_source, msg_value) .await; if reply.is_err() { - msg::send_with_gas(msg_source, Ok::(NftMarketplaceEvent::ValueSent), 0, msg_value) - .expect("Error in sending value"); + msg::send_with_gas( + msg_source, + Ok::(NftMarketplaceEvent::ValueSent), + 0, + msg_value, + ) + .expect("Error in sending value"); } reply } @@ -163,8 +173,13 @@ async fn main() { .buy(collection_address, token_id, msg_source, msg_value) .await; if reply.is_err() { - msg::send_with_gas(msg_source, Ok::(NftMarketplaceEvent::ValueSent), 0, msg_value) - .expect("Error in sending value"); + msg::send_with_gas( + msg_source, + Ok::(NftMarketplaceEvent::ValueSent), + 0, + msg_value, + ) + .expect("Error in sending value"); } reply } @@ -187,8 +202,13 @@ async fn main() { let reply = nft_marketplace.add_bid(collection_address, token_id, msg_source, msg_value); if reply.is_err() { - msg::send_with_gas(msg_source, Ok::(NftMarketplaceEvent::ValueSent), 0, msg_value) - .expect("Error in sending value"); + msg::send_with_gas( + msg_source, + Ok::(NftMarketplaceEvent::ValueSent), + 0, + msg_value, + ) + .expect("Error in sending value"); } reply } @@ -219,8 +239,13 @@ async fn main() { .create_offer(collection_address, token_id, msg_source, msg_value) .await; if reply.is_err() { - msg::send_with_gas(msg_source, Ok::(NftMarketplaceEvent::ValueSent), 0, msg_value) - .expect("Error in sending value"); + msg::send_with_gas( + msg_source, + Ok::(NftMarketplaceEvent::ValueSent), + 0, + msg_value, + ) + .expect("Error in sending value"); } reply } @@ -558,8 +583,13 @@ impl NftMarketplace { return Err(NftMarketplaceError::InsufficientBalance); } let value = balance - existential_deposit; - msg::send_with_gas(msg_src, Ok::(NftMarketplaceEvent::ValueSent), 0, value) - .expect("Error in sending value"); + msg::send_with_gas( + msg_src, + Ok::(NftMarketplaceEvent::ValueSent), + 0, + value, + ) + .expect("Error in sending value"); Ok(NftMarketplaceEvent::BalanceHasBeenWithdrawn { value }) } diff --git a/src/offer.rs b/src/offer.rs index 56b9deb..78f5c3d 100644 --- a/src/offer.rs +++ b/src/offer.rs @@ -60,8 +60,13 @@ impl NftMarketplace { self.offers .entry(offer.clone()) .and_modify(|price| { - msg::send_with_gas(offer.creator, Ok::(NftMarketplaceEvent::ValueSent), 0, *price) - .expect("Error in sending value"); + msg::send_with_gas( + offer.creator, + Ok::(NftMarketplaceEvent::ValueSent), + 0, + *price, + ) + .expect("Error in sending value"); *price = msg_value; }) .or_insert(msg_value); @@ -88,8 +93,13 @@ impl NftMarketplace { if let Some((offer, price)) = self.offers.get_key_value(&offer) { // use send_with_gas to transfer the value directly to the balance, not to the mailbox. - msg::send_with_gas(offer.creator, Ok::(NftMarketplaceEvent::ValueSent), 0, *price) - .expect("Error in sending value"); + msg::send_with_gas( + offer.creator, + Ok::(NftMarketplaceEvent::ValueSent), + 0, + *price, + ) + .expect("Error in sending value"); } else { return Err(NftMarketplaceError::WrongDataOffer); } diff --git a/src/payment.rs b/src/payment.rs index a5ef4e7..ef37a7a 100644 --- a/src/payment.rs +++ b/src/payment.rs @@ -1,6 +1,6 @@ use gcore::exec; use gstd::{msg, ActorId}; -use nft_marketplace_io::{NftMarketplaceEvent, NftMarketplaceError}; +use nft_marketplace_io::{NftMarketplaceError, NftMarketplaceEvent}; pub fn currency_transfer( collection_owner: ActorId, diff --git a/tests/test_nft.rs b/tests/test_nft.rs index 4f17bd7..e5b6abe 100644 --- a/tests/test_nft.rs +++ b/tests/test_nft.rs @@ -2,7 +2,8 @@ use crate::utils::*; use utils::prelude::*; mod utils; use gtest::Program; -use nft_io::{AdditionalLinks, Config, ImageData, NftAction, NftError, NftInit, NftState, +use nft_io::{ + AdditionalLinks, Config, ImageData, NftAction, NftError, NftInit, NftState, StateQuery as StateQueryNft, StateReply as StateReplyNft, }; use nft_marketplace_io::*; @@ -85,6 +86,7 @@ fn successful_basics() { payment_for_mint: 0, transferable: Some(0), sellable: Some(0), + variable_meta: false, }; let res = nft_collection.send(USERS[0], NftAction::ChangeConfig { config }); assert!(!res.main_failed()); @@ -324,6 +326,7 @@ fn failures() { payment_for_mint: 0, transferable: Some(0), sellable: Some(0), + variable_meta: false, }; let res = nft_collection.send(USERS[0], NftAction::ChangeConfig { config }); check_nft_error(USERS[0], &res, NftError::ConfigCannotBeChanged); @@ -706,17 +709,191 @@ fn get_state(nft_collection: &Program) -> Option { None } +#[test] +fn test_variable_nft() { + let sys = utils::initialize_system(); + let marketplace = Program::init_marketplace(&sys); + let nft_collection_code_id = + sys.submit_code("target/wasm32-unknown-unknown/debug/nft.opt.wasm"); + + let state_reply = marketplace + .read_state(StateQuery::CollectionsInfo) + .expect("Unexpected invalid state."); + if let StateReply::CollectionsInfo(state) = state_reply { + assert!(state.is_empty(), "Collection info should be empty"); + } + // Successful addition of a new collection + let name_simple_nft = "Simple NFT".to_string(); + marketplace.add_new_collection( + ADMINS[0], + nft_collection_code_id.into_bytes().into(), + name_simple_nft.clone(), + None, + ); + + let state_reply = marketplace + .read_state(StateQuery::CollectionsInfo) + .expect("Unexpected invalid state."); + if let StateReply::CollectionsInfo(state) = state_reply { + assert!(!state.is_empty(), "Collection info shouldn't be empty"); + } + // Successful creation of a new collection + let init_nft_payload = get_init_nft_payload(USERS[0].into(), 0, Some(3), 0, None); + sys.mint_to(USERS[0], 100_000_000_000_000); + marketplace.create_collection( + USERS[0], + name_simple_nft.clone(), + init_nft_payload.encode(), + 10_000_000_000_000, + None, + ); + + let state_reply = marketplace + .read_state(StateQuery::AllCollections) + .expect("Unexpected invalid state."); + let address_nft = if let StateReply::AllCollections(state) = state_reply { + assert!(!state.is_empty(), "Collections shouldn't be empty"); + state[0].0 + } else { + assert!(false, "Unexpected StateReply variant"); + 0.into() + }; + + let address_nft_list: [u8; 32] = address_nft.into(); + let nft_collection = sys.get_program(address_nft_list); + let state_reply = nft_collection + .read_state(StateQueryNft::All) + .expect("Unexpected invalid state."); + if let StateReplyNft::All(state) = state_reply { + assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); + } + + // Successful change config in the new collection + let config = Config { + name: "My Collection".to_string(), + description: "My Collection".to_string(), + collection_banner: "Collection banner".to_string(), + collection_logo: "Collection logo".to_string(), + collection_tags: vec!["tag1".to_string()], + additional_links: None, + royalty: 0, + user_mint_limit: 3.into(), + payment_for_mint: 0, + transferable: Some(0), + sellable: Some(0), + variable_meta: true, + }; + let res = nft_collection.send(USERS[0], NftAction::ChangeConfig { config }); + assert!(!res.main_failed()); + let state_reply = nft_collection + .read_state(StateQueryNft::All) + .expect("Unexpected invalid state."); + if let StateReplyNft::All(state) = state_reply { + assert_eq!(state.config.name, "My Collection".to_string()); + assert_eq!(state.config.description, "My Collection".to_string()); + } + + // Successful mint NFT in the new collection + marketplace.mint(USERS[1], address_nft, None); + + let state_reply = nft_collection + .read_state(StateQueryNft::All) + .expect("Unexpected invalid state."); + if let StateReplyNft::All(state) = state_reply { + assert_eq!(state.collection_owner, USERS[0].into(), "Wrong Admin"); + let (owner, token_id) = state.owners.get(0).expect("Can't be None"); + assert_eq!(*owner, USERS[1].into(), "Wrong owner"); + assert_eq!(*token_id, vec![0], "Wrong token id"); + } + + // Successful add metadata NFT in the collection + let res = nft_collection.send( + USERS[0], + NftAction::AddMetadata { + nft_id: 0, + metadata: "additional metadata".to_string(), + }, + ); + assert!(!res.main_failed()); + + let state_reply = nft_collection + .read_state(StateQueryNft::All) + .expect("Unexpected invalid state."); + if let StateReplyNft::All(state) = state_reply { + assert_eq!( + state.tokens[0].1.metadata, + vec!["additional metadata".to_string()] + ); + } + + // Successful change image link + let res = nft_collection.send( + USERS[0], + NftAction::ChangeImageLink { + nft_id: 0, + img_link: "new image link".to_string(), + }, + ); + assert!(!res.main_failed()); + + let state_reply = nft_collection + .read_state(StateQueryNft::All) + .expect("Unexpected invalid state."); + if let StateReplyNft::All(state) = state_reply { + assert_eq!(state.tokens[0].1.media_url, "new image link".to_string()); + } + + // Successful change metadata + let res = nft_collection.send( + USERS[0], + NftAction::ChangeMetadata { + nft_id: 0, + metadata: vec!["new metadata".to_string()], + }, + ); + assert!(!res.main_failed()); + + let state_reply = nft_collection + .read_state(StateQueryNft::All) + .expect("Unexpected invalid state."); + if let StateReplyNft::All(state) = state_reply { + assert_eq!(state.tokens[0].1.metadata, vec!["new metadata".to_string()]); + } + + // Successful delete metadata + let res = nft_collection.send(USERS[0], NftAction::DeleteMetadata { nft_id: 0 }); + assert!(!res.main_failed()); + + let state_reply = nft_collection + .read_state(StateQueryNft::All) + .expect("Unexpected invalid state."); + if let StateReplyNft::All(state) = state_reply { + let expected_vec: Vec = vec![]; + assert_eq!(state.tokens[0].1.metadata, expected_vec); + } +} + // TODO // #[test] // fn admin_features() { // } +// #[test] +// fn check() { +// let input = "01feda1100feda11006606010018b274b99cebf93ae8bbfcacfcc164aee3a9b8044185d3b506e78191070085510c284e4654206d696e7465640000d827a4da8e01000040506c61796564204361722052616365730100782ba6da8e01000048506c61796564205469632d5461632d546f6501009021a7da8e01000000"; +// let decoded = hex::decode(input).expect("Decoding failed"); +// let mut res: &[u8] = &decoded; +// let result = Result::::decode(&mut res).ok(); +// println!("RES: {:?}", result); +// } + #[test] fn check() { - let input = "13"; + let input = "d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d10546573740c31323300d4697066733a2f2f516d553546584866427966744b3866364466416f674851426865617a514d506345506444564a414a366845357764d4697066733a2f2f516d665567414c4a31754a4150444b387855533250614a71444b524348474865766b6f4e556d4a326a3278374854000101000100010001000100000000000000000000000000000000000000000004d4697066733a2f2f516d657844756669556b4d45793869636641396d396b6d3544374837644762615455514b696853336e4a517961670000"; let decoded = hex::decode(input).expect("Decoding failed"); let mut res: &[u8] = &decoded; - let result = Result::::decode(&mut res).ok(); + // let result = Result::::decode(&mut res).ok(); + let result = NftInit::decode(&mut res).ok(); println!("RES: {:?}", result); } diff --git a/tests/utils/mod.rs b/tests/utils/mod.rs index 0a8ed83..7aaf3ba 100644 --- a/tests/utils/mod.rs +++ b/tests/utils/mod.rs @@ -1,11 +1,14 @@ +use crate::codec::Error; use gstd::{ActorId, CodeId, Encode}; use gtest::{Program, RunResult, System}; +use music_nft_io::{ + Config as MusicConfig, ImageData as MusicImageData, Links, ListenCapability, MusicNftError, + MusicNftEvent, MusicNftInit, +}; use nft_io::{Config, ImageData, NftError, NftEvent, NftInit}; -use music_nft_io::{MusicNftInit, MusicNftError, MusicNftEvent, Links, Config as MusicConfig, ListenCapability, ImageData as MusicImageData}; use nft_marketplace_io::{ NftMarketplaceAction, NftMarketplaceError, NftMarketplaceEvent, NftMarketplaceInit, Offer, }; -use crate::codec::Error; mod common; pub mod prelude; @@ -163,7 +166,7 @@ impl NftMarketplace for Program<'_> { meta_link: meta_link.clone(), type_name: type_name.clone(), type_description: type_description.clone(), - allow_create: true + allow_create: true, }, ); assert!(!res.main_failed()); @@ -295,7 +298,7 @@ impl NftMarketplace for Program<'_> { collection_address, token_id, min_price, - duration_ms: (duration+1) * 3000, + duration_ms: (duration + 1) * 3000, }) }; let result = &res.decoded_log::>(); @@ -486,7 +489,7 @@ impl NftMarketplace for Program<'_> { ms_in_block, fee_per_uploaded_file: None, max_creator_royalty: None, - max_number_of_images: None + max_number_of_images: None, }, ); assert!(!res.main_failed()); @@ -533,7 +536,6 @@ pub fn check_music_nft_error(from: u64, result: &RunResult, error: MusicNftError assert!(result.contains(&(from, Err::(error).encode()))); } - pub fn get_init_nft_payload( collection_owner: ActorId, royalty: u16, @@ -562,6 +564,7 @@ pub fn get_init_nft_payload( payment_for_mint, transferable: Some(0), sellable: Some(0), + variable_meta: false, }, img_links_and_data, permission_to_mint, @@ -603,7 +606,7 @@ pub fn get_init_music_nft_payload( payment_for_mint, transferable: Some(0), sellable: Some(0), - listening_capabilities: ListenCapability::Demo + listening_capabilities: ListenCapability::Demo, }, links_and_data, permission_to_mint, diff --git a/tests/utils_gclient/mod.rs b/tests/utils_gclient/mod.rs index 327506a..6ca7cdf 100644 --- a/tests/utils_gclient/mod.rs +++ b/tests/utils_gclient/mod.rs @@ -79,12 +79,12 @@ pub async fn init_marketplace(api: &GearApi) -> Result<(MessageId, ProgramId), ( let royalty_to_marketplace = 200; let init_marketplace = NftMarketplaceInit { - gas_for_creation: 110_000_000_000, - gas_for_mint: 10_000_000_000, // 7_000_000_000 - gas_for_transfer_token: 6_000_000_000, // 4_000_000_000 - gas_for_close_auction: 13_000_000_000, // 15_000_000_000 - gas_for_delete_collection: 5_000_000_000, // 3_000_000_000 - gas_for_get_info: 7_000_000_000, // 5_000_000_000 + gas_for_creation: 110_000_000_000, + gas_for_mint: 10_000_000_000, // 7_000_000_000 + gas_for_transfer_token: 6_000_000_000, // 4_000_000_000 + gas_for_close_auction: 13_000_000_000, // 15_000_000_000 + gas_for_delete_collection: 5_000_000_000, // 3_000_000_000 + gas_for_get_info: 7_000_000_000, // 5_000_000_000 time_between_create_collections: 3_600_000, // 1 hour in milliseconds royalty_to_marketplace_for_trade: royalty_to_marketplace, royalty_to_marketplace_for_mint: royalty_to_marketplace, @@ -122,27 +122,15 @@ pub async fn init_marketplace(api: &GearApi) -> Result<(MessageId, ProgramId), ( let allow_message_payload = NftMarketplaceAction::AllowMessage(true); let gas_info = api - .calculate_handle_gas( - None, - program_id, - allow_message_payload.encode(), - 0, - true, - ) + .calculate_handle_gas(None, program_id, allow_message_payload.encode(), 0, true) .await .expect("Error calculate gas"); - + let (message_id, _) = api - .send_message( - program_id, - allow_message_payload, - gas_info.min_limit, - 0, - ) + .send_message(program_id, allow_message_payload, gas_info.min_limit, 0) .await .expect("Error send message"); - let allow_create_collection_payload = NftMarketplaceAction::AllowCreateCollection(true); let gas_info = api @@ -155,7 +143,7 @@ pub async fn init_marketplace(api: &GearApi) -> Result<(MessageId, ProgramId), ( ) .await .expect("Error calculate gas"); - + let (message_id, _) = api .send_message( program_id, @@ -185,7 +173,7 @@ pub async fn add_new_collection( meta_link: String::from("My Meta"), type_name: String::from("Simple NFT"), type_description: String::from("My Collection"), - allow_create: true + allow_create: true, }; let gas_info = api @@ -220,7 +208,7 @@ pub async fn create_collection( limit_copies: Some(1), }; let number_of_image = 10_000; - let fee = number_of_image * 282_857_142_900; + let fee = number_of_image * 282_857_142_900; let img_links_and_data: Vec<(String, ImageData)> = (0..number_of_image) .map(|i| (format!("Img-{}", i), img_data.clone())) .collect(); @@ -239,6 +227,7 @@ pub async fn create_collection( payment_for_mint: 10_000_000_000_000, transferable: Some(0), sellable: Some(0), + variable_meta: false, }, img_links_and_data, permission_to_mint: None, @@ -362,9 +351,7 @@ pub async fn get_new_client(api: &GearApi, name: &str) -> GearApi { .await .expect("Error transfer"); - api.clone() - .with(name) - .expect("Unable to change signer.") + api.clone().with(name).expect("Unable to change signer.") } pub fn get_program_id_from_u64(u64: u64) -> ProgramId { @@ -372,4 +359,3 @@ pub fn get_program_id_from_u64(u64: u64) -> ProgramId { let list: [u8; 32] = actor_id.into(); list.into() } -