From 9db7549e803ebba8341854f2d3eb04e3c2195f64 Mon Sep 17 00:00:00 2001 From: Gangov <6922910+gangov@users.noreply.github.com> Date: Wed, 14 Aug 2024 13:30:25 +0300 Subject: [PATCH 01/25] refactor as per PR --- contracts/auctions/src/lib.rs | 2 +- contracts/collections/src/contract.rs | 34 ++++++++++++++++++++++--- contracts/collections/src/storage.rs | 3 ++- contracts/collections/src/test/setup.rs | 15 ++++------- contracts/collections/src/test/tests.rs | 10 +++----- 5 files changed, 43 insertions(+), 21 deletions(-) diff --git a/contracts/auctions/src/lib.rs b/contracts/auctions/src/lib.rs index d90052e5..1bb3d393 100644 --- a/contracts/auctions/src/lib.rs +++ b/contracts/auctions/src/lib.rs @@ -1,6 +1,6 @@ #![no_std] -use soroban_sdk::{contract, contractimpl, contracttype, Address, BytesN, Env, Vec}; +use soroban_sdk::{contract, contractimpl, contracttype, Address, Env, Vec}; #[derive(Clone)] #[contracttype] diff --git a/contracts/collections/src/contract.rs b/contracts/collections/src/contract.rs index a5978efd..198303a1 100644 --- a/contracts/collections/src/contract.rs +++ b/contracts/collections/src/contract.rs @@ -19,9 +19,9 @@ impl Collections { env: Env, admin: Address, name: String, - image: URIValue, + symbol: String, ) -> Result<(), ContractError> { - let config = Config { name, image }; + let config = Config { name, symbol }; save_config(&env, config)?; save_admin(&env, &admin)?; @@ -46,7 +46,6 @@ impl Collections { log!(&env, "Collections: Balance of batch: length missmatch"); return Err(ContractError::AccountsIdsLengthMissmatch); } - let mut batch_balances: Vec = vec![&env]; // we verified that the length of both `accounts` and `ids` is the same @@ -313,6 +312,24 @@ impl Collections { Ok(()) } + // Sets the main image(logo) for the collection + #[allow(dead_code)] + pub fn set_collection_uri(env: Env, sender: Address, uri: Bytes) -> Result<(), ContractError> { + sender.require_auth(); + //TODO: maybe the below comparison is not needed + let admin = get_admin(&env)?; + if admin != sender { + log!(&env, "Collections: Set uri: Unauthorized"); + return Err(ContractError::Unauthorized); + } + + env.storage() + .persistent() + .set(&DataKey::CollectionUri, &URIValue { uri }); + + Ok(()) + } + // Returns the URI for a token type `id` #[allow(dead_code)] pub fn uri(env: Env, id: u64) -> Result { @@ -324,6 +341,17 @@ impl Collections { } } + // Returns the URI for a token type `id` + #[allow(dead_code)] + pub fn collection_uri(env: Env) -> Result { + if let Some(uri) = env.storage().persistent().get(&DataKey::CollectionUri) { + Ok(uri) + } else { + log!(&env, "Collections: Uri: No collection uri set"); + Err(ContractError::NoUriSet) + } + } + #[cfg(test)] #[allow(dead_code)] pub fn show_admin(env: &Env) -> Result { diff --git a/contracts/collections/src/storage.rs b/contracts/collections/src/storage.rs index 3fb03351..de5f7ea1 100644 --- a/contracts/collections/src/storage.rs +++ b/contracts/collections/src/storage.rs @@ -32,6 +32,7 @@ pub enum DataKey { Balance(BalanceDataKey), OperatorApproval(OperatorApprovalKey), Uri(NftId), + CollectionUri, Config, } @@ -46,7 +47,7 @@ pub struct URIValue { #[contracttype] pub struct Config { pub name: String, - pub image: URIValue, + pub symbol: String, } pub const ADMIN: Symbol = symbol_short!("admin"); diff --git a/contracts/collections/src/test/setup.rs b/contracts/collections/src/test/setup.rs index 1badc2af..210e7cfe 100644 --- a/contracts/collections/src/test/setup.rs +++ b/contracts/collections/src/test/setup.rs @@ -1,27 +1,22 @@ -use soroban_sdk::{testutils::Address as _, Address, Bytes, Env, String}; +use soroban_sdk::{testutils::Address as _, Address, Env, String}; -use crate::{ - contract::{Collections, CollectionsClient}, - storage::URIValue, -}; +use crate::contract::{Collections, CollectionsClient}; pub fn initialize_collection_contract<'a>( env: &Env, admin: Option<&Address>, name: Option<&String>, - image: Option<&URIValue>, + symbol: Option<&String>, ) -> CollectionsClient<'a> { let collections = CollectionsClient::new(env, &env.register_contract(None, Collections {})); let alt_admin = &Address::generate(env); let alt_name = &String::from_str(env, "Stellar kitties"); - let alt_image = URIValue { - uri: Bytes::from_slice(env, &[64]), - }; + let alt_symbol = &String::from_str(env, "STK"); let admin = admin.unwrap_or(alt_admin); let name = name.unwrap_or(alt_name); - let image = image.unwrap_or(&alt_image); + let image = symbol.unwrap_or(alt_symbol); collections.initialize(admin, name, image); diff --git a/contracts/collections/src/test/tests.rs b/contracts/collections/src/test/tests.rs index 86a5978a..12b49152 100644 --- a/contracts/collections/src/test/tests.rs +++ b/contracts/collections/src/test/tests.rs @@ -10,14 +10,12 @@ fn proper_initialization() { env.mock_all_auths(); let admin = Address::generate(&env); - let uri_value = URIValue { - uri: Bytes::from_slice(&env, &[64]), - }; let name = &String::from_str(&env, "Stellar kitties"); + let symbol = &String::from_str(&env, "STK"); let collections_client = - initialize_collection_contract(&env, Some(&admin), Some(name), Some(&uri_value)); + initialize_collection_contract(&env, Some(&admin), Some(name), Some(symbol)); let actual_admin_addr = collections_client.show_admin(); assert_eq!(admin, actual_admin_addr); @@ -25,11 +23,11 @@ fn proper_initialization() { let actual_config = collections_client.show_config(); let expected_config = Config { name: name.clone(), - image: uri_value, + symbol: symbol.clone(), }; assert_eq!(actual_config.name, expected_config.name); - assert_eq!(actual_config.image, expected_config.image); + assert_eq!(actual_config.symbol, expected_config.symbol); } #[test] From f8a9be82edf4b73a978d6608daeeddc9fc368272 Mon Sep 17 00:00:00 2001 From: Gangov <6922910+gangov@users.noreply.github.com> Date: Wed, 14 Aug 2024 18:24:34 +0300 Subject: [PATCH 02/25] refactors the key used to store the balance of nfts --- contracts/collections/src/contract.rs | 11 ++---- contracts/collections/src/error.rs | 1 + contracts/collections/src/storage.rs | 50 ++++++++++++------------- contracts/collections/src/test/tests.rs | 25 +++++++------ 4 files changed, 44 insertions(+), 43 deletions(-) diff --git a/contracts/collections/src/contract.rs b/contracts/collections/src/contract.rs index 198303a1..5e01dcd5 100644 --- a/contracts/collections/src/contract.rs +++ b/contracts/collections/src/contract.rs @@ -201,13 +201,11 @@ impl Collections { let admin = get_admin(&env)?; if admin != sender { - log!(&env, "Collections: Set uri: Unauthorized"); + log!(&env, "Collections: Mint: Unauthorized"); return Err(ContractError::Unauthorized); } - let current_balance = get_balance_of(&env, &to, id)?; - //TODO: check for overflow? - update_balance_of(&env, &to, id, current_balance + amount)?; + update_balance_of(&env, &to, id, amount)?; Ok(()) } @@ -225,7 +223,7 @@ impl Collections { let admin = get_admin(&env)?; if admin != sender { - log!(&env, "Collections: Set uri: Unauthorized"); + log!(&env, "Collections: Mint batch: Unauthorized"); return Err(ContractError::Unauthorized); } @@ -238,9 +236,8 @@ impl Collections { let id = ids.get(idx).unwrap(); let amount = amounts.get(idx).unwrap(); - let current_balance = get_balance_of(&env, &to, id)?; //TODO: check for overflow? - update_balance_of(&env, &to, id, current_balance + amount)?; + update_balance_of(&env, &to, id, amount)?; } Ok(()) diff --git a/contracts/collections/src/error.rs b/contracts/collections/src/error.rs index 203b7cee..924fe61b 100644 --- a/contracts/collections/src/error.rs +++ b/contracts/collections/src/error.rs @@ -14,4 +14,5 @@ pub enum ContractError { Unauthorized = 7, InvalidAccountIndex = 8, InvalidIdIndex = 9, + IdNotFound = 10, } diff --git a/contracts/collections/src/storage.rs b/contracts/collections/src/storage.rs index de5f7ea1..791e2a94 100644 --- a/contracts/collections/src/storage.rs +++ b/contracts/collections/src/storage.rs @@ -8,14 +8,8 @@ use soroban_sdk::{contracttype, symbol_short, Address, Bytes, String, Symbol}; //pub(crate) const BALANCE_LIFETIME_THRESHOLD: u32 = BALANCE_BUMP_AMOUNT - DAY_IN_LEDGERS; type NftId = u64; - -// Struct to represent a token balance for a specific address and token ID -#[derive(Clone)] -#[contracttype] -pub struct BalanceDataKey { - pub token_id: u64, - pub owner: Address, -} +type TokenId = u64; +type Balance = u64; // Struct to represent the operator approval status #[derive(Clone)] @@ -29,7 +23,7 @@ pub struct OperatorApprovalKey { #[derive(Clone)] #[contracttype] pub enum DataKey { - Balance(BalanceDataKey), + Balance(Address), OperatorApproval(OperatorApprovalKey), Uri(NftId), CollectionUri, @@ -53,23 +47,25 @@ pub struct Config { pub const ADMIN: Symbol = symbol_short!("admin"); pub mod utils { - use soroban_sdk::{log, Address, Env}; + use soroban_sdk::{log, Address, Env, Map}; use crate::error::ContractError; - use super::{Config, DataKey, ADMIN}; + use super::{Balance, Config, DataKey, TokenId, ADMIN}; pub fn get_balance_of(env: &Env, owner: &Address, id: u64) -> Result { - let result = env + let balance_map = env .storage() .persistent() - .get(&DataKey::Balance(crate::storage::BalanceDataKey { - token_id: id, - owner: owner.clone(), - })) - .unwrap_or(0u64); + .get(&DataKey::Balance(owner.clone())) + .unwrap_or(Map::new(env)); - Ok(result) + if let Some(balance) = balance_map.get(id) { + Ok(balance) + } else { + log!(&env, "Id not found!"); + Err(ContractError::IdNotFound) + } } pub fn update_balance_of( @@ -78,13 +74,17 @@ pub mod utils { id: u64, new_amount: u64, ) -> Result<(), ContractError> { - env.storage().persistent().set( - &DataKey::Balance(crate::storage::BalanceDataKey { - token_id: id, - owner: owner.clone(), - }), - &new_amount, - ); + let mut balance_map: Map = env + .storage() + .persistent() + .get(&DataKey::Balance(owner.clone())) + .unwrap_or(Map::new(env)); + + balance_map.set(id, new_amount); + + env.storage() + .persistent() + .set(&DataKey::Balance(owner.clone()), &balance_map); Ok(()) } diff --git a/contracts/collections/src/test/tests.rs b/contracts/collections/src/test/tests.rs index 12b49152..a831913d 100644 --- a/contracts/collections/src/test/tests.rs +++ b/contracts/collections/src/test/tests.rs @@ -45,6 +45,7 @@ fn mint_and_check_balance() { collections_client.mint(&admin, &user, &2, &10); assert_eq!(collections_client.balance_of(&user, &1), 10); + assert_eq!(collections_client.balance_of(&user, &2), 10); } #[test] @@ -53,28 +54,30 @@ fn mint_batch_and_balance_of_batch() { env.mock_all_auths(); let admin = Address::generate(&env); - let user = Address::generate(&env); + let user_a = Address::generate(&env); + let user_b = Address::generate(&env); + let user_c = Address::generate(&env); + let user_d = Address::generate(&env); + let user_e = Address::generate(&env); let id_list = vec![&env, 1, 2, 3, 4, 5]; let amounts_list = vec![&env, 10, 20, 30, 40, 50]; let collections_client = initialize_collection_contract(&env, Some(&admin), None, None); - collections_client.mint_batch(&admin, &user, &id_list, &amounts_list); + collections_client.mint_batch(&admin, &user_a, &id_list, &amounts_list); + collections_client.mint_batch(&admin, &user_b, &id_list, &amounts_list); + collections_client.mint_batch(&admin, &user_c, &id_list, &amounts_list); + collections_client.mint_batch(&admin, &user_d, &id_list, &amounts_list); + collections_client.mint_batch(&admin, &user_e, &id_list, &amounts_list); let actual = collections_client.balance_of_batch( - &vec![ - &env, - user, - Address::generate(&env), - Address::generate(&env), - Address::generate(&env), - Address::generate(&env), - ], + &vec![&env, user_a, user_b, user_c, user_d, user_e], &id_list, ); - assert_eq!(vec![&env, 10, 0, 0, 0, 0], actual); + // here we compare what amount of each nft_id does each user has + assert_eq!(vec![&env, 10, 20, 30, 40, 50], actual); } #[test] From 9071b64a0276cf4ce34d674342b5c8f5e323d1ae Mon Sep 17 00:00:00 2001 From: Gangov <6922910+gangov@users.noreply.github.com> Date: Wed, 14 Aug 2024 18:28:37 +0300 Subject: [PATCH 03/25] adds upgrade entrypoint --- contracts/collections/src/contract.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/contracts/collections/src/contract.rs b/contracts/collections/src/contract.rs index 5e01dcd5..1fcf8231 100644 --- a/contracts/collections/src/contract.rs +++ b/contracts/collections/src/contract.rs @@ -1,4 +1,4 @@ -use soroban_sdk::{contract, contractimpl, log, vec, Address, Bytes, Env, String, Vec}; +use soroban_sdk::{contract, contractimpl, log, vec, Address, Bytes, BytesN, Env, String, Vec}; use crate::{ error::ContractError, @@ -349,6 +349,16 @@ impl Collections { } } + #[allow(dead_code)] + pub fn upgrade(env: Env, new_wasm_hash: BytesN<32>) -> Result<(), ContractError> { + let admin: Address = get_admin(&env)?; + admin.require_auth(); + + env.deployer().update_current_contract_wasm(new_wasm_hash); + + Ok(()) + } + #[cfg(test)] #[allow(dead_code)] pub fn show_admin(env: &Env) -> Result { From ed7f96a2769d0572913b405279c669f4d8b81848 Mon Sep 17 00:00:00 2001 From: Gangov <6922910+gangov@users.noreply.github.com> Date: Wed, 14 Aug 2024 19:06:17 +0300 Subject: [PATCH 04/25] update --- contracts/collections/src/contract.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/contracts/collections/src/contract.rs b/contracts/collections/src/contract.rs index 1fcf8231..e4ecd74e 100644 --- a/contracts/collections/src/contract.rs +++ b/contracts/collections/src/contract.rs @@ -311,14 +311,9 @@ impl Collections { // Sets the main image(logo) for the collection #[allow(dead_code)] - pub fn set_collection_uri(env: Env, sender: Address, uri: Bytes) -> Result<(), ContractError> { - sender.require_auth(); + pub fn set_collection_uri(env: Env, uri: Bytes) -> Result<(), ContractError> { //TODO: maybe the below comparison is not needed - let admin = get_admin(&env)?; - if admin != sender { - log!(&env, "Collections: Set uri: Unauthorized"); - return Err(ContractError::Unauthorized); - } + get_admin(&env)?.require_auth(); env.storage() .persistent() From fadde09788895f4c277c17ed5e5696e663e87799 Mon Sep 17 00:00:00 2001 From: Kaloyan Gangov <6922910+gangov@users.noreply.github.com> Date: Thu, 15 Aug 2024 10:20:07 +0300 Subject: [PATCH 05/25] adds is_initialized check and tests for it --- contracts/collections/src/contract.rs | 12 +++++++++++- contracts/collections/src/error.rs | 1 + contracts/collections/src/storage.rs | 13 +++++++++++++ contracts/collections/src/test/tests.rs | 26 ++++++++++++++++++++++++- 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/contracts/collections/src/contract.rs b/contracts/collections/src/contract.rs index e4ecd74e..1d5495b2 100644 --- a/contracts/collections/src/contract.rs +++ b/contracts/collections/src/contract.rs @@ -3,7 +3,10 @@ use soroban_sdk::{contract, contractimpl, log, vec, Address, Bytes, BytesN, Env, use crate::{ error::ContractError, storage::{ - utils::{get_admin, get_balance_of, save_admin, save_config, update_balance_of}, + utils::{ + get_admin, get_balance_of, is_initialized, save_admin, save_config, set_initialized, + update_balance_of, + }, Config, DataKey, OperatorApprovalKey, URIValue, }, }; @@ -23,9 +26,16 @@ impl Collections { ) -> Result<(), ContractError> { let config = Config { name, symbol }; + if is_initialized(&env) { + log!(&env, "Collections: Initialize: Already initialized"); + return Err(ContractError::AlreadyInitialized); + } + save_config(&env, config)?; save_admin(&env, &admin)?; + set_initialized(&env); + Ok(()) } diff --git a/contracts/collections/src/error.rs b/contracts/collections/src/error.rs index 924fe61b..2a3175fc 100644 --- a/contracts/collections/src/error.rs +++ b/contracts/collections/src/error.rs @@ -15,4 +15,5 @@ pub enum ContractError { InvalidAccountIndex = 8, InvalidIdIndex = 9, IdNotFound = 10, + AlreadyInitialized = 11, } diff --git a/contracts/collections/src/storage.rs b/contracts/collections/src/storage.rs index 791e2a94..5cadf600 100644 --- a/contracts/collections/src/storage.rs +++ b/contracts/collections/src/storage.rs @@ -28,6 +28,7 @@ pub enum DataKey { Uri(NftId), CollectionUri, Config, + IsInitialized, } // Struct to represent token URI @@ -119,4 +120,16 @@ pub mod utils { Err(ContractError::AdminNotSet) } } + pub fn is_initialized(env: &Env) -> bool { + env.storage() + .persistent() + .get(&DataKey::IsInitialized) + .unwrap_or(false) + } + + pub fn set_initialized(env: &Env) { + env.storage() + .persistent() + .set(&DataKey::IsInitialized, &true); + } } diff --git a/contracts/collections/src/test/tests.rs b/contracts/collections/src/test/tests.rs index a831913d..ad6d1dab 100644 --- a/contracts/collections/src/test/tests.rs +++ b/contracts/collections/src/test/tests.rs @@ -1,6 +1,10 @@ use soroban_sdk::{testutils::Address as _, vec, Address, Bytes, Env, String}; -use crate::storage::{Config, URIValue}; +use crate::{ + contract::{Collections, CollectionsClient}, + error::ContractError, + storage::{Config, URIValue}, +}; use super::setup::initialize_collection_contract; @@ -30,6 +34,26 @@ fn proper_initialization() { assert_eq!(actual_config.symbol, expected_config.symbol); } +#[test] +fn initialization_should_fail_when_done_twice() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + + let name = &String::from_str(&env, "Stellar kitties"); + let symbol = &String::from_str(&env, "STK"); + + let collections = CollectionsClient::new(&env, &env.register_contract(None, Collections {})); + + collections.initialize(&admin, name, symbol); + + assert_eq!( + collections.try_initialize(&admin, name, symbol), + Err(Ok(ContractError::AlreadyInitialized)) + ); +} + #[test] fn mint_and_check_balance() { let env = Env::default(); From 222289307171be1c45a8291038dc7b9a304ff2a4 Mon Sep 17 00:00:00 2001 From: Kaloyan Gangov <6922910+gangov@users.noreply.github.com> Date: Thu, 15 Aug 2024 10:32:16 +0300 Subject: [PATCH 06/25] refactor as per PR comments --- contracts/collections/src/error.rs | 3 ++- contracts/collections/src/storage.rs | 14 ++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/contracts/collections/src/error.rs b/contracts/collections/src/error.rs index 2a3175fc..7f85b28b 100644 --- a/contracts/collections/src/error.rs +++ b/contracts/collections/src/error.rs @@ -14,6 +14,7 @@ pub enum ContractError { Unauthorized = 7, InvalidAccountIndex = 8, InvalidIdIndex = 9, - IdNotFound = 10, + NftIdNotFound = 10, AlreadyInitialized = 11, + EntryDoesNotExist = 12, } diff --git a/contracts/collections/src/storage.rs b/contracts/collections/src/storage.rs index 5cadf600..0e616f99 100644 --- a/contracts/collections/src/storage.rs +++ b/contracts/collections/src/storage.rs @@ -55,17 +55,23 @@ pub mod utils { use super::{Balance, Config, DataKey, TokenId, ADMIN}; pub fn get_balance_of(env: &Env, owner: &Address, id: u64) -> Result { - let balance_map = env + let balance_map: Map = env .storage() .persistent() .get(&DataKey::Balance(owner.clone())) - .unwrap_or(Map::new(env)); + .unwrap_or_else(|| { + log!( + &env, + "Collections: Get balance of: Entry with this address is not present" + ); + Err(ContractError::EntryDoesNotExist) + })?; if let Some(balance) = balance_map.get(id) { Ok(balance) } else { - log!(&env, "Id not found!"); - Err(ContractError::IdNotFound) + log!(&env, "No entry for the given NFT Id"); + Err(ContractError::NftIdNotFound) } } From 7f1e3aba1d905ea94450f7ed56f8a3b6428a8638 Mon Sep 17 00:00:00 2001 From: Kaloyan Gangov <6922910+gangov@users.noreply.github.com> Date: Thu, 15 Aug 2024 12:37:56 +0300 Subject: [PATCH 07/25] failing path for batch_balance_of --- contracts/collections/src/test/tests.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/contracts/collections/src/test/tests.rs b/contracts/collections/src/test/tests.rs index ad6d1dab..73e67dde 100644 --- a/contracts/collections/src/test/tests.rs +++ b/contracts/collections/src/test/tests.rs @@ -214,3 +214,21 @@ fn test_uri() { assert_eq!(collections_client.uri(&1), URIValue { uri: secret_uri }); } + +#[test] +fn balance_of_batch_should_fail_when_different_sizes_for_accounts_and_ids() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let user = Address::generate(&env); + + let collections_client = initialize_collection_contract(&env, Some(&admin), None, None); + + // not neceserally to mint, as we expect the test to fali + + assert_eq!( + collections_client.try_balance_of_batch(&vec![&env, user], &vec![&env, 1, 2, 3]), + Err(Ok(ContractError::AccountsIdsLengthMissmatch)) + ) +} From ff8dc8c5f47fbfac97462fd115cbbdd5219211e6 Mon Sep 17 00:00:00 2001 From: Kaloyan Gangov <6922910+gangov@users.noreply.github.com> Date: Thu, 15 Aug 2024 12:42:36 +0300 Subject: [PATCH 08/25] failing test case for when the user tries to authorize himself --- contracts/collections/src/test/tests.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/contracts/collections/src/test/tests.rs b/contracts/collections/src/test/tests.rs index 73e67dde..e2a85340 100644 --- a/contracts/collections/src/test/tests.rs +++ b/contracts/collections/src/test/tests.rs @@ -216,7 +216,7 @@ fn test_uri() { } #[test] -fn balance_of_batch_should_fail_when_different_sizes_for_accounts_and_ids() { +fn should_fail_when_balance_of_batch_has_different_sizes_for_accounts_and_ids() { let env = Env::default(); env.mock_all_auths(); @@ -225,10 +225,25 @@ fn balance_of_batch_should_fail_when_different_sizes_for_accounts_and_ids() { let collections_client = initialize_collection_contract(&env, Some(&admin), None, None); - // not neceserally to mint, as we expect the test to fali + // not neceserally to mint anything, as we expect the test to fali assert_eq!( collections_client.try_balance_of_batch(&vec![&env, user], &vec![&env, 1, 2, 3]), Err(Ok(ContractError::AccountsIdsLengthMissmatch)) ) } + +#[test] +fn should_fail_when_set_approval_for_all_tries_to_approve_self() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + + let collections_client = initialize_collection_contract(&env, Some(&admin), None, None); + + assert_eq!( + collections_client.try_set_approval_for_all(&admin, &admin, &true), + Err(Ok(ContractError::CannotApproveSelf)) + ) +} From 29320e1fa63f3fe5ac722cf0b92b2cda93a00113 Mon Sep 17 00:00:00 2001 From: Kaloyan Gangov <6922910+gangov@users.noreply.github.com> Date: Thu, 15 Aug 2024 13:02:30 +0300 Subject: [PATCH 09/25] adds tests for when the user doesn't have enough to spend --- contracts/collections/src/contract.rs | 1 - contracts/collections/src/test/tests.rs | 40 +++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/contracts/collections/src/contract.rs b/contracts/collections/src/contract.rs index 1d5495b2..81a5f82e 100644 --- a/contracts/collections/src/contract.rs +++ b/contracts/collections/src/contract.rs @@ -128,7 +128,6 @@ impl Collections { to: Address, id: u64, transfer_amount: u64, - _data: Bytes, // we don't have onERC1155Received in Stellar/Soroban ) -> Result<(), ContractError> { from.require_auth(); // TODO: check if `to` is not zero address diff --git a/contracts/collections/src/test/tests.rs b/contracts/collections/src/test/tests.rs index e2a85340..a2fb403e 100644 --- a/contracts/collections/src/test/tests.rs +++ b/contracts/collections/src/test/tests.rs @@ -247,3 +247,43 @@ fn should_fail_when_set_approval_for_all_tries_to_approve_self() { Err(Ok(ContractError::CannotApproveSelf)) ) } + +#[test] +fn should_fail_when_get_balance_cannot_find_datakey_in_safe_transfer_from() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let user = Address::generate(&env); + + let collections_client = initialize_collection_contract(&env, Some(&admin), None, None); + + assert_eq!( + collections_client.try_safe_transfer_from(&user, &Address::generate(&env), &1, &1), + Err(Ok(ContractError::EntryDoesNotExist)) + ) +} + +#[test] +fn should_fail_when_sender_balance_not_enough() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let user_a = Address::generate(&env); + let user_b = Address::generate(&env); + + let client = initialize_collection_contract(&env, Some(&admin), None, None); + + // mint 1 + client.mint(&admin, &user_a, &1, &1); + client.mint(&admin, &user_b, &1, &1); + + assert_eq!(client.balance_of(&user_a, &1), 1u64); + + // try to send 10 + assert_eq!( + client.try_safe_transfer_from(&user_a, &user_b, &1, &10), + Err(Ok(ContractError::InsufficientBalance)) + ) +} From 8d736401557f3902884924d9eb95eaf58bb2e69e Mon Sep 17 00:00:00 2001 From: Kaloyan Gangov <6922910+gangov@users.noreply.github.com> Date: Thu, 15 Aug 2024 13:52:19 +0300 Subject: [PATCH 10/25] adds a test for safe_transfer --- contracts/collections/src/test/tests.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/contracts/collections/src/test/tests.rs b/contracts/collections/src/test/tests.rs index a2fb403e..3774060f 100644 --- a/contracts/collections/src/test/tests.rs +++ b/contracts/collections/src/test/tests.rs @@ -119,6 +119,29 @@ fn approval_tests() { assert!(collectoins_client.is_approved_for_all(&user, &operator)); } +#[test] +fn safe_transfer_from() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let user_a = Address::generate(&env); + let user_b = Address::generate(&env); + + let client = initialize_collection_contract(&env, Some(&admin), None, None); + + client.mint(&admin, &user_a, &1, &1); + client.mint(&admin, &user_b, &2, &1); + + assert_eq!(client.balance_of(&user_a, &1), 1u64); + assert_eq!(client.balance_of(&user_b, &2), 1u64); + + client.safe_transfer_from(&user_a, &user_b, &1, &1); + + assert_eq!(client.balance_of(&user_a, &1), 0u64); + assert_eq!(client.balance_of(&user_b, &1), 1u64); +} + #[test] fn burning() { let env = Env::default(); From cd380b284adad45564f732e788c3f5396123f40f Mon Sep 17 00:00:00 2001 From: Kaloyan Gangov <6922910+gangov@users.noreply.github.com> Date: Thu, 15 Aug 2024 14:06:45 +0300 Subject: [PATCH 11/25] removes a test and refactors the logic on how we handle transfer --- contracts/collections/src/storage.rs | 11 ++--------- contracts/collections/src/test/tests.rs | 19 +------------------ 2 files changed, 3 insertions(+), 27 deletions(-) diff --git a/contracts/collections/src/storage.rs b/contracts/collections/src/storage.rs index 0e616f99..2d65581f 100644 --- a/contracts/collections/src/storage.rs +++ b/contracts/collections/src/storage.rs @@ -59,19 +59,12 @@ pub mod utils { .storage() .persistent() .get(&DataKey::Balance(owner.clone())) - .unwrap_or_else(|| { - log!( - &env, - "Collections: Get balance of: Entry with this address is not present" - ); - Err(ContractError::EntryDoesNotExist) - })?; + .unwrap_or(Map::new(env)); if let Some(balance) = balance_map.get(id) { Ok(balance) } else { - log!(&env, "No entry for the given NFT Id"); - Err(ContractError::NftIdNotFound) + Ok(0u64) } } diff --git a/contracts/collections/src/test/tests.rs b/contracts/collections/src/test/tests.rs index 3774060f..e687cdc7 100644 --- a/contracts/collections/src/test/tests.rs +++ b/contracts/collections/src/test/tests.rs @@ -131,10 +131,9 @@ fn safe_transfer_from() { let client = initialize_collection_contract(&env, Some(&admin), None, None); client.mint(&admin, &user_a, &1, &1); - client.mint(&admin, &user_b, &2, &1); assert_eq!(client.balance_of(&user_a, &1), 1u64); - assert_eq!(client.balance_of(&user_b, &2), 1u64); + assert_eq!(client.balance_of(&user_b, &1), 0u64); client.safe_transfer_from(&user_a, &user_b, &1, &1); @@ -271,22 +270,6 @@ fn should_fail_when_set_approval_for_all_tries_to_approve_self() { ) } -#[test] -fn should_fail_when_get_balance_cannot_find_datakey_in_safe_transfer_from() { - let env = Env::default(); - env.mock_all_auths(); - - let admin = Address::generate(&env); - let user = Address::generate(&env); - - let collections_client = initialize_collection_contract(&env, Some(&admin), None, None); - - assert_eq!( - collections_client.try_safe_transfer_from(&user, &Address::generate(&env), &1, &1), - Err(Ok(ContractError::EntryDoesNotExist)) - ) -} - #[test] fn should_fail_when_sender_balance_not_enough() { let env = Env::default(); From 80d7efa1a2721c1a4efb70245995b3ba8985409b Mon Sep 17 00:00:00 2001 From: Kaloyan Gangov <6922910+gangov@users.noreply.github.com> Date: Thu, 15 Aug 2024 14:55:36 +0300 Subject: [PATCH 12/25] safe batch transfer test --- contracts/collections/src/contract.rs | 1 - contracts/collections/src/test/tests.rs | 37 +++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/contracts/collections/src/contract.rs b/contracts/collections/src/contract.rs index 81a5f82e..bac292d2 100644 --- a/contracts/collections/src/contract.rs +++ b/contracts/collections/src/contract.rs @@ -158,7 +158,6 @@ impl Collections { to: Address, ids: Vec, amounts: Vec, - _data: Bytes, // we don't have onERC1155Received in Stellar/Soroban ) -> Result<(), ContractError> { from.require_auth(); // TODO: check if `to` is not zero address diff --git a/contracts/collections/src/test/tests.rs b/contracts/collections/src/test/tests.rs index e687cdc7..c3371537 100644 --- a/contracts/collections/src/test/tests.rs +++ b/contracts/collections/src/test/tests.rs @@ -141,6 +141,43 @@ fn safe_transfer_from() { assert_eq!(client.balance_of(&user_b, &1), 1u64); } +#[test] +fn safe_batch_transfer() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let user_a = Address::generate(&env); + let user_b = Address::generate(&env); + + let client = initialize_collection_contract(&env, Some(&admin), None, None); + + let ids = vec![&env, 1, 2, 3, 4, 5]; + let amounts = vec![&env, 5, 5, 5, 5, 5]; + client.mint_batch(&admin, &user_a, &ids, &amounts); + + // NOTE: I don't see a reason why the length of the address should match the length of the + // ids, but this is as close to the `ERC1155` implementation as possible. + let accounts = vec![ + &env, + user_a.clone(), + user_b.clone(), + Address::generate(&env), + Address::generate(&env), + Address::generate(&env), + ]; + assert_eq!( + client.balance_of_batch(&accounts, &ids), + vec![&env, 5, 0, 0, 0, 0] + ); + + client.safe_batch_transfer_from(&user_a, &user_b, &ids, &amounts); + assert_eq!( + client.balance_of_batch(&accounts, &ids), + vec![&env, 0, 5, 0, 0, 0] + ); +} + #[test] fn burning() { let env = Env::default(); From dd84a141ea9e2e0e202b033d3e796a723222cc76 Mon Sep 17 00:00:00 2001 From: Kaloyan Gangov <6922910+gangov@users.noreply.github.com> Date: Thu, 15 Aug 2024 15:05:10 +0300 Subject: [PATCH 13/25] more tests for the failing path when dealing with safe_batch_transfer --- contracts/collections/src/test/tests.rs | 51 +++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/contracts/collections/src/test/tests.rs b/contracts/collections/src/test/tests.rs index c3371537..d2b80b70 100644 --- a/contracts/collections/src/test/tests.rs +++ b/contracts/collections/src/test/tests.rs @@ -330,3 +330,54 @@ fn should_fail_when_sender_balance_not_enough() { Err(Ok(ContractError::InsufficientBalance)) ) } + +#[test] +fn safe_batch_transfer_should_fail_when_id_mismatch() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let user_a = Address::generate(&env); + + let client = initialize_collection_contract(&env, Some(&admin), None, None); + + let ids = vec![&env, 1, 2, 3, 4, 5]; + let amounts = vec![&env, 5, 5, 5, 5, 5]; + client.mint_batch(&admin, &user_a, &ids, &amounts); + + assert_eq!( + client.try_safe_batch_transfer_from( + &user_a, + &Address::generate(&env), + &ids, + // only 4 amounts, when 5 are needed + &vec![&env, 10, 10, 10, 10], + ), + Err(Ok(ContractError::IdsAmountsLengthMismatch)) + ); +} + +#[test] +fn safe_batch_transfer_should_fail_when_insufficient_balance() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let user_a = Address::generate(&env); + + let client = initialize_collection_contract(&env, Some(&admin), None, None); + + let ids = vec![&env, 1, 2, 3, 4, 5]; + let amounts = vec![&env, 5, 5, 5, 5, 5]; + client.mint_batch(&admin, &user_a, &ids, &amounts); + + assert_eq!( + client.try_safe_batch_transfer_from( + &user_a, + &Address::generate(&env), + &ids, + &vec![&env, 10, 10, 10, 10, 10], + ), + Err(Ok(ContractError::InsufficientBalance)) + ); +} From 9ffe070d3b5013e8df13b78540c5f29a94cafa22 Mon Sep 17 00:00:00 2001 From: Kaloyan Gangov <6922910+gangov@users.noreply.github.com> Date: Thu, 15 Aug 2024 15:10:35 +0300 Subject: [PATCH 14/25] adds a failing test case for when the user is not authorized to mint --- contracts/collections/src/test/tests.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/contracts/collections/src/test/tests.rs b/contracts/collections/src/test/tests.rs index d2b80b70..a31badcf 100644 --- a/contracts/collections/src/test/tests.rs +++ b/contracts/collections/src/test/tests.rs @@ -381,3 +381,16 @@ fn safe_batch_transfer_should_fail_when_insufficient_balance() { Err(Ok(ContractError::InsufficientBalance)) ); } + +#[test] +fn mint_should_fail_when_unauthorized() { + let env = Env::default(); + env.mock_all_auths(); + + let client = initialize_collection_contract(&env, None, None, None); + + assert_eq!( + client.try_mint(&Address::generate(&env), &Address::generate(&env), &1, &1), + Err(Ok(ContractError::Unauthorized)) + ); +} From 250dbf576cc0286b93bebe9a48154149c3c011df Mon Sep 17 00:00:00 2001 From: Kaloyan Gangov <6922910+gangov@users.noreply.github.com> Date: Thu, 15 Aug 2024 15:21:58 +0300 Subject: [PATCH 15/25] more tests for failing test case when batch_minting --- contracts/collections/src/test/tests.rs | 37 +++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/contracts/collections/src/test/tests.rs b/contracts/collections/src/test/tests.rs index a31badcf..3b47b30a 100644 --- a/contracts/collections/src/test/tests.rs +++ b/contracts/collections/src/test/tests.rs @@ -394,3 +394,40 @@ fn mint_should_fail_when_unauthorized() { Err(Ok(ContractError::Unauthorized)) ); } + +#[test] +fn mint_batch_should_fail_when_unauthorized() { + let env = Env::default(); + env.mock_all_auths(); + + let client = initialize_collection_contract(&env, None, None, None); + + assert_eq!( + client.try_mint_batch( + &Address::generate(&env), + &Address::generate(&env), + &vec![&env, 1], + &vec![&env, 1] + ), + Err(Ok(ContractError::Unauthorized)) + ); +} + +#[test] +fn mint_batch_should_fail_when_different_lengths_of_vecs() { + let env = Env::default(); + env.mock_all_auths(); + let admin = Address::generate(&env); + + let client = initialize_collection_contract(&env, Some(&admin), None, None); + + assert_eq!( + client.try_mint_batch( + &admin, + &Address::generate(&env), + &vec![&env, 1, 2], + &vec![&env, 1] + ), + Err(Ok(ContractError::IdsAmountsLengthMismatch)) + ); +} From 263c4fe5473ad73e55e94e7bfc918452845d75bc Mon Sep 17 00:00:00 2001 From: Kaloyan Gangov <6922910+gangov@users.noreply.github.com> Date: Thu, 15 Aug 2024 15:25:21 +0300 Subject: [PATCH 16/25] test for when burn fails --- contracts/collections/src/test/tests.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/contracts/collections/src/test/tests.rs b/contracts/collections/src/test/tests.rs index 3b47b30a..9818c45a 100644 --- a/contracts/collections/src/test/tests.rs +++ b/contracts/collections/src/test/tests.rs @@ -431,3 +431,17 @@ fn mint_batch_should_fail_when_different_lengths_of_vecs() { Err(Ok(ContractError::IdsAmountsLengthMismatch)) ); } + +#[test] +fn burn_should_fail_when_not_enough_balance() { + let env = Env::default(); + env.mock_all_auths(); + let user = Address::generate(&env); + + let client = initialize_collection_contract(&env, Some(&user), None, None); + + assert_eq!( + client.try_burn(&user, &1, &1), + Err(Ok(ContractError::InsufficientBalance)) + ); +} From 7f0aedd3298104914f644fb41408a1e934aeb1ff Mon Sep 17 00:00:00 2001 From: Kaloyan Gangov <6922910+gangov@users.noreply.github.com> Date: Thu, 15 Aug 2024 15:30:37 +0300 Subject: [PATCH 17/25] more tests for when batch_burn fails --- contracts/collections/src/test/tests.rs | 28 +++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/contracts/collections/src/test/tests.rs b/contracts/collections/src/test/tests.rs index 9818c45a..37097ebf 100644 --- a/contracts/collections/src/test/tests.rs +++ b/contracts/collections/src/test/tests.rs @@ -445,3 +445,31 @@ fn burn_should_fail_when_not_enough_balance() { Err(Ok(ContractError::InsufficientBalance)) ); } + +#[test] +fn burn_batch_should_fail_when_vec_length_missmatch() { + let env = Env::default(); + env.mock_all_auths(); + + let user = Address::generate(&env); + let client = initialize_collection_contract(&env, Some(&user), None, None); + + assert_eq!( + client.try_burn_batch(&user, &vec![&env, 1, 2], &vec![&env, 1]), + Err(Ok(ContractError::IdsAmountsLengthMismatch)) + ); +} + +#[test] +fn burn_batch_should_fail_when_not_enough_balance() { + let env = Env::default(); + env.mock_all_auths(); + + let user = Address::generate(&env); + let client = initialize_collection_contract(&env, Some(&user), None, None); + + assert_eq!( + client.try_burn_batch(&user, &vec![&env, 1], &vec![&env, 1]), + Err(Ok(ContractError::InsufficientBalance)) + ); +} From 15e6afc4b07aadc15bb5c45b41fb5400f5694909 Mon Sep 17 00:00:00 2001 From: Kaloyan Gangov <6922910+gangov@users.noreply.github.com> Date: Thu, 15 Aug 2024 15:35:32 +0300 Subject: [PATCH 18/25] test for when set_uri fails --- contracts/collections/src/contract.rs | 1 - contracts/collections/src/test/tests.rs | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/contracts/collections/src/contract.rs b/contracts/collections/src/contract.rs index bac292d2..223026fc 100644 --- a/contracts/collections/src/contract.rs +++ b/contracts/collections/src/contract.rs @@ -320,7 +320,6 @@ impl Collections { // Sets the main image(logo) for the collection #[allow(dead_code)] pub fn set_collection_uri(env: Env, uri: Bytes) -> Result<(), ContractError> { - //TODO: maybe the below comparison is not needed get_admin(&env)?.require_auth(); env.storage() diff --git a/contracts/collections/src/test/tests.rs b/contracts/collections/src/test/tests.rs index 37097ebf..6a1c64f6 100644 --- a/contracts/collections/src/test/tests.rs +++ b/contracts/collections/src/test/tests.rs @@ -473,3 +473,21 @@ fn burn_batch_should_fail_when_not_enough_balance() { Err(Ok(ContractError::InsufficientBalance)) ); } + +#[test] +fn set_uri_should_fail_when_unauthorized() { + let env = Env::default(); + env.mock_all_auths(); + + let user = Address::generate(&env); + let client = initialize_collection_contract(&env, Some(&user), None, None); + + assert_eq!( + client.try_set_uri( + &Address::generate(&env), + &1, + &Bytes::from_slice(&env, &[42]) + ), + Err(Ok(ContractError::Unauthorized)) + ) +} From ada922a30b5185c4b6a1f3242cc58f839ad4af17 Mon Sep 17 00:00:00 2001 From: Kaloyan Gangov <6922910+gangov@users.noreply.github.com> Date: Thu, 15 Aug 2024 15:46:55 +0300 Subject: [PATCH 19/25] test for set collection uri --- contracts/collections/src/test/tests.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/contracts/collections/src/test/tests.rs b/contracts/collections/src/test/tests.rs index 6a1c64f6..7f7ca6c1 100644 --- a/contracts/collections/src/test/tests.rs +++ b/contracts/collections/src/test/tests.rs @@ -274,6 +274,24 @@ fn test_uri() { assert_eq!(collections_client.uri(&1), URIValue { uri: secret_uri }); } +#[test] +fn set_collection_uri_should_work() { + let env = Env::default(); + env.mock_all_auths(); + + let user = Address::generate(&env); + let client = initialize_collection_contract(&env, Some(&user), None, None); + + assert_eq!( + client.try_collection_uri(), + Err(Ok(ContractError::NoUriSet)) + ); + let uri = Bytes::from_slice(&env, &[42]); + client.set_collection_uri(&uri); + + assert_eq!(client.collection_uri(), URIValue { uri }); +} + #[test] fn should_fail_when_balance_of_batch_has_different_sizes_for_accounts_and_ids() { let env = Env::default(); From bc06a71f3a020e9ccf3a37b1bce449dad37742d7 Mon Sep 17 00:00:00 2001 From: Kaloyan Gangov <6922910+gangov@users.noreply.github.com> Date: Thu, 15 Aug 2024 15:53:17 +0300 Subject: [PATCH 20/25] test case for when looking for a uri when such is not set --- contracts/collections/src/test/tests.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/contracts/collections/src/test/tests.rs b/contracts/collections/src/test/tests.rs index 7f7ca6c1..fe9fbc80 100644 --- a/contracts/collections/src/test/tests.rs +++ b/contracts/collections/src/test/tests.rs @@ -509,3 +509,14 @@ fn set_uri_should_fail_when_unauthorized() { Err(Ok(ContractError::Unauthorized)) ) } + +#[test] +fn uri_should_fail_when_none_set() { + let env = Env::default(); + env.mock_all_auths(); + + let user = Address::generate(&env); + let client = initialize_collection_contract(&env, Some(&user), None, None); + + assert_eq!(client.try_uri(&1), Err(Ok(ContractError::NoUriSet))) +} From 8b6f9727c1f017037cac749c066c5143625ad868 Mon Sep 17 00:00:00 2001 From: Kaloyan Gangov <6922910+gangov@users.noreply.github.com> Date: Thu, 15 Aug 2024 16:04:43 +0300 Subject: [PATCH 21/25] adds a few tarpaulin macros to ignore test cases which we cannot cover --- contracts/collections/src/contract.rs | 1 + contracts/collections/src/storage.rs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/contracts/collections/src/contract.rs b/contracts/collections/src/contract.rs index 223026fc..e2f08653 100644 --- a/contracts/collections/src/contract.rs +++ b/contracts/collections/src/contract.rs @@ -352,6 +352,7 @@ impl Collections { } #[allow(dead_code)] + #[cfg(not(tarpaulin_include))] pub fn upgrade(env: Env, new_wasm_hash: BytesN<32>) -> Result<(), ContractError> { let admin: Address = get_admin(&env)?; admin.require_auth(); diff --git a/contracts/collections/src/storage.rs b/contracts/collections/src/storage.rs index 2d65581f..d4a6ed96 100644 --- a/contracts/collections/src/storage.rs +++ b/contracts/collections/src/storage.rs @@ -96,6 +96,7 @@ pub mod utils { } #[allow(dead_code)] + #[cfg(not(tarpaulin_include))] pub fn get_config(env: &Env) -> Result { if let Some(config) = env.storage().persistent().get(&DataKey::Config) { Ok(config) @@ -111,6 +112,7 @@ pub mod utils { Ok(()) } + #[cfg(not(tarpaulin_include))] pub fn get_admin(env: &Env) -> Result { if let Some(admin) = env.storage().persistent().get(&ADMIN) { Ok(admin) From 55a32ed4e8166c58e459981e774839cedbbbb703 Mon Sep 17 00:00:00 2001 From: Kaloyan Gangov <6922910+gangov@users.noreply.github.com> Date: Fri, 16 Aug 2024 10:56:29 +0300 Subject: [PATCH 22/25] adds events --- contracts/collections/src/contract.rs | 66 +++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 5 deletions(-) diff --git a/contracts/collections/src/contract.rs b/contracts/collections/src/contract.rs index e2f08653..a90508a8 100644 --- a/contracts/collections/src/contract.rs +++ b/contracts/collections/src/contract.rs @@ -24,7 +24,10 @@ impl Collections { name: String, symbol: String, ) -> Result<(), ContractError> { - let config = Config { name, symbol }; + let config = Config { + name: name.clone(), + symbol: symbol.clone(), + }; if is_initialized(&env) { log!(&env, "Collections: Initialize: Already initialized"); @@ -36,6 +39,11 @@ impl Collections { set_initialized(&env); + env.events() + .publish(("initialize", "collection name: "), name); + env.events() + .publish(("initialize", "collectoin symbol: "), symbol); + Ok(()) } @@ -92,12 +100,21 @@ impl Collections { env.storage().persistent().set( &DataKey::OperatorApproval(OperatorApprovalKey { - owner: sender, - operator, + owner: sender.clone(), + operator: operator.clone(), }), &approved, ); + env.events() + .publish(("Set approval for", "Sender: "), sender); + env.events().publish( + ("Set approval for", "Set approval for operator: "), + operator, + ); + env.events() + .publish(("Set approval for", "New approval: "), approved); + Ok(()) } @@ -147,6 +164,12 @@ impl Collections { // next we incrase the recipient's `to` balance update_balance_of(&env, &to, id, rcpt_balance + transfer_amount)?; + env.events().publish(("safe transfer from", "from: "), from); + env.events().publish(("safe transfer from", "to: "), to); + env.events().publish(("safe transfer from", "id: "), id); + env.events() + .publish(("safe transfer from", "transfer amount: "), transfer_amount); + Ok(()) } @@ -192,6 +215,15 @@ impl Collections { update_balance_of(&env, &to, id, rcpt_balance + amount)?; } + env.events() + .publish(("safe batch transfer from", "from: "), from); + env.events() + .publish(("safe batch transfer from", "to: "), to); + env.events() + .publish(("safe batch transfer from", "ids: "), ids); + env.events() + .publish(("safe batch transfer from", "amounts: "), amounts); + Ok(()) } @@ -215,6 +247,11 @@ impl Collections { update_balance_of(&env, &to, id, amount)?; + env.events().publish(("mint", "sender: "), sender); + env.events().publish(("mint", "to: "), to); + env.events().publish(("mint", "id: "), id); + env.events().publish(("mint", "amount: "), amount); + Ok(()) } @@ -248,6 +285,11 @@ impl Collections { update_balance_of(&env, &to, id, amount)?; } + env.events().publish(("mint batch", "sender: "), sender); + env.events().publish(("mint batch", "to: "), to); + env.events().publish(("mint batch", "ids: "), ids); + env.events().publish(("mint batch", "amounts: "), amounts); + Ok(()) } @@ -266,6 +308,10 @@ impl Collections { update_balance_of(&env, &from, id, current_balance - amount)?; + env.events().publish(("burn", "from: "), from); + env.events().publish(("burn", "id: "), id); + env.events().publish(("burn", "amount: "), amount); + Ok(()) } @@ -297,6 +343,10 @@ impl Collections { update_balance_of(&env, &from, id, current_balance - amount)?; } + env.events().publish(("burn batch", "from: "), from); + env.events().publish(("burn batch", "ids: "), ids); + env.events().publish(("burn batch", "amounts: "), amounts); + Ok(()) } @@ -312,7 +362,11 @@ impl Collections { env.storage() .persistent() - .set(&DataKey::Uri(id), &URIValue { uri }); + .set(&DataKey::Uri(id), &URIValue { uri: uri.clone() }); + + env.events().publish(("set uri", "sender: "), sender); + env.events().publish(("set uri", "id: "), id); + env.events().publish(("set uri", "uri: "), uri); Ok(()) } @@ -324,7 +378,9 @@ impl Collections { env.storage() .persistent() - .set(&DataKey::CollectionUri, &URIValue { uri }); + .set(&DataKey::CollectionUri, &URIValue { uri: uri.clone() }); + + env.events().publish(("set collection uri", "uri: "), uri); Ok(()) } From 08603cc621424af3455faf22bfb72d2750e0f71c Mon Sep 17 00:00:00 2001 From: Kaloyan Gangov <6922910+gangov@users.noreply.github.com> Date: Fri, 16 Aug 2024 13:19:11 +0300 Subject: [PATCH 23/25] removes unnecessary comment --- contracts/collections/src/test/tests.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/contracts/collections/src/test/tests.rs b/contracts/collections/src/test/tests.rs index fe9fbc80..23a54a30 100644 --- a/contracts/collections/src/test/tests.rs +++ b/contracts/collections/src/test/tests.rs @@ -149,6 +149,9 @@ fn safe_batch_transfer() { let admin = Address::generate(&env); let user_a = Address::generate(&env); let user_b = Address::generate(&env); + let user_c = Address::generate(&env); + let user_d = Address::generate(&env); + let user_e = Address::generate(&env); let client = initialize_collection_contract(&env, Some(&admin), None, None); @@ -156,16 +159,7 @@ fn safe_batch_transfer() { let amounts = vec![&env, 5, 5, 5, 5, 5]; client.mint_batch(&admin, &user_a, &ids, &amounts); - // NOTE: I don't see a reason why the length of the address should match the length of the - // ids, but this is as close to the `ERC1155` implementation as possible. - let accounts = vec![ - &env, - user_a.clone(), - user_b.clone(), - Address::generate(&env), - Address::generate(&env), - Address::generate(&env), - ]; + let accounts = vec![&env, user_a.clone(), user_b.clone(), user_c, user_d, user_e]; assert_eq!( client.balance_of_batch(&accounts, &ids), vec![&env, 5, 0, 0, 0, 0] From 10bb2dc6e1c9abfeaa66ad9cde43b74a7763e841 Mon Sep 17 00:00:00 2001 From: Kaloyan Gangov <6922910+gangov@users.noreply.github.com> Date: Fri, 16 Aug 2024 13:27:54 +0300 Subject: [PATCH 24/25] adds README.md --- contracts/collections/README.md | 258 ++++++++++++++++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 contracts/collections/README.md diff --git a/contracts/collections/README.md b/contracts/collections/README.md new file mode 100644 index 00000000..fd2c96e5 --- /dev/null +++ b/contracts/collections/README.md @@ -0,0 +1,258 @@ + +# Collections + +## Main functionality +This is a contract for managing collections of tokens, similar to the ERC-1155 standard. It allows for the creation, transfer, and management of multiple token types within a single contract. + +## Messages: + +`initialize` + +Params: +- `admin`: `Address` of the administrator for the collection +- `name`: `String` name of the collection +- `symbol`: `String` symbol for the collection + +Return type: +`Result<(), ContractError>` + +Description: +Initializes the collection contract with an admin, name, and symbol. + +
+ +`balance_of` + +Params: +- `account`: `Address` of the account to check +- `id`: `u64` ID of the token type + +Return type: +`Result` + +Description: +Returns the balance of a specific token type for a given account. + +
+ +`balance_of_batch` + +Params: +- `accounts`: `Vec
` list of accounts to check +- `ids`: `Vec` list of token type IDs + +Return type: +`Result, ContractError>` + +Description: +Returns the balances of multiple token types for multiple accounts. + +
+ +`set_approval_for_all` + +Params: +- `sender`: `Address` of the account granting approval +- `operator`: `Address` of the account being approved +- `approved`: `bool` approval status + +Return type: +`Result<(), ContractError>` + +Description: +Grants or revokes permission for an operator to manage the sender's tokens. + +
+ +`is_approved_for_all` + +Params: +- `owner`: `Address` of the token owner +- `operator`: `Address` of the potential operator + +Return type: +`Result` + +Description: +Checks if an operator is approved to manage an owner's tokens. + +
+ +`safe_transfer_from` + +Params: +- `from`: `Address` of the sender +- `to`: `Address` of the recipient +- `id`: `u64` ID of the token type +- `transfer_amount`: `u64` amount to transfer + +Return type: +`Result<(), ContractError>` + +Description: +Transfers tokens of a specific type from one address to another. + +
+ +`safe_batch_transfer_from` + +Params: +- `from`: `Address` of the sender +- `to`: `Address` of the recipient +- `ids`: `Vec` list of token type IDs +- `amounts`: `Vec` list of amounts to transfer + +Return type: +`Result<(), ContractError>` + +Description: +Transfers multiple types and amounts of tokens from one address to another. + +
+ +`mint` + +Params: +- `sender`: `Address` of the minting authority +- `to`: `Address` of the recipient +- `id`: `u64` ID of the token type +- `amount`: `u64` amount to mint + +Return type: +`Result<(), ContractError>` + +Description: +Mints new tokens of a specific type to a recipient. + +
+ +`mint_batch` + +Params: +- `sender`: `Address` of the minting authority +- `to`: `Address` of the recipient +- `ids`: `Vec` list of token type IDs +- `amounts`: `Vec` list of amounts to mint + +Return type: +`Result<(), ContractError>` + +Description: +Mints multiple types and amounts of tokens to a recipient. + +
+ +`burn` + +Params: +- `from`: `Address` of the token holder +- `id`: `u64` ID of the token type +- `amount`: `u64` amount to burn + +Return type: +`Result<(), ContractError>` + +Description: +Burns (destroys) tokens of a specific type from an address. + +
+ +`burn_batch` + +Params: +- `from`: `Address` of the token holder +- `ids`: `Vec` list of token type IDs +- `amounts`: `Vec` list of amounts to burn + +Return type: +`Result<(), ContractError>` + +Description: +Burns multiple types and amounts of tokens from an address. + +
+ +`set_uri` + +Params: +- `sender`: `Address` of the authority +- `id`: `u64` ID of the token type +- `uri`: `Bytes` URI for the token type + +Return type: +`Result<(), ContractError>` + +Description: +Sets the URI for a specific token type. + +
+ +`set_collection_uri` + +Params: +- `uri`: `Bytes` URI for the collection + +Return type: +`Result<(), ContractError>` + +Description: +Sets the main image (logo) URI for the entire collection. + +
+ +`uri` + +Params: +- `id`: `u64` ID of the token type + +Return type: +`Result` + +Description: +Retrieves the URI for a specific token type. + +
+ +`collection_uri` + +Params: +None + +Return type: +`Result` + +Description: +Retrieves the URI for the entire collection. + +
+ +`upgrade` + +Params: +- `new_wasm_hash`: `BytesN<32>` hash of the new contract WASM + +Return type: +`Result<(), ContractError>` + +Description: +Upgrades the contract to a new WASM implementation. + +
+ +## Internal Structs + +```rust +pub struct Config { + pub name: String, + pub symbol: String, +} + +pub struct URIValue { + pub uri: Bytes, +} + +pub struct OperatorApprovalKey { + pub owner: Address, + pub operator: Address, +} +``` From f131f66cc67b227c9d8a77fd248605b69c66810a Mon Sep 17 00:00:00 2001 From: Kaloyan Gangov <6922910+gangov@users.noreply.github.com> Date: Mon, 19 Aug 2024 17:22:25 +0300 Subject: [PATCH 25/25] changes to a test --- Cargo.lock | 34 +++++++++++++++++++++++++ contracts/collections/Cargo.toml | 1 + contracts/collections/src/test/tests.rs | 15 ++++++++--- 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a89a1624..4eadf95b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -717,6 +717,7 @@ name = "phoenix-nft-collections" version = "1.0.0" dependencies = [ "soroban-sdk", + "test-case", ] [[package]] @@ -1203,6 +1204,39 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "test-case" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8" +dependencies = [ + "test-case-macros", +] + +[[package]] +name = "test-case-core" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "test-case-macros" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "test-case-core", +] + [[package]] name = "thiserror" version = "1.0.55" diff --git a/contracts/collections/Cargo.toml b/contracts/collections/Cargo.toml index f1f64bf5..89af9076 100644 --- a/contracts/collections/Cargo.toml +++ b/contracts/collections/Cargo.toml @@ -17,3 +17,4 @@ soroban-sdk = { workspace = true } [dev_dependencies] soroban-sdk = { workspace = true, features = ["testutils"] } +test-case = "3.3.1" diff --git a/contracts/collections/src/test/tests.rs b/contracts/collections/src/test/tests.rs index 23a54a30..dd05d777 100644 --- a/contracts/collections/src/test/tests.rs +++ b/contracts/collections/src/test/tests.rs @@ -7,6 +7,7 @@ use crate::{ }; use super::setup::initialize_collection_contract; +use test_case::test_case; #[test] fn proper_initialization() { @@ -369,8 +370,16 @@ fn safe_batch_transfer_should_fail_when_id_mismatch() { ); } -#[test] -fn safe_batch_transfer_should_fail_when_insufficient_balance() { +#[test_case(10, 10, 10, 10, 10; "very greedy")] +#[test_case(5, 4, 3, 2, 10; "just a single case is greedy")] +#[test_case(1, 1, 10, 1, 1; "same as the previous")] +fn safe_batch_transfer_should_fail_when_insufficient_balance( + amount_a: u64, + amount_b: u64, + amount_c: u64, + amount_d: u64, + amount_e: u64, +) { let env = Env::default(); env.mock_all_auths(); @@ -388,7 +397,7 @@ fn safe_batch_transfer_should_fail_when_insufficient_balance() { &user_a, &Address::generate(&env), &ids, - &vec![&env, 10, 10, 10, 10, 10], + &vec![&env, amount_a, amount_b, amount_c, amount_d, amount_e], ), Err(Ok(ContractError::InsufficientBalance)) );