diff --git a/contracts/assetsup/src/asset.rs b/contracts/assetsup/src/asset.rs index 255a410..42c5d91 100644 --- a/contracts/assetsup/src/asset.rs +++ b/contracts/assetsup/src/asset.rs @@ -1,11 +1,13 @@ -use soroban_sdk::{Address, BytesN, String, contracttype}; +use soroban_sdk::{Address, BytesN, String, Vec, contracttype}; -use crate::types::{AssetStatus, AssetType}; +use crate::types::{AssetStatus, CustomAttribute}; #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] pub enum DataKey { Asset(BytesN<32>), + OwnerRegistry(Address), + AssetCounter, } #[contracttype] @@ -13,17 +15,25 @@ pub enum DataKey { pub struct Asset { pub id: BytesN<32>, pub name: String, - pub asset_type: AssetType, + pub description: String, pub category: String, - pub branch_id: BytesN<32>, - pub department_id: u64, + pub owner: Address, + pub registration_timestamp: u64, + pub last_transfer_timestamp: u64, pub status: AssetStatus, - pub purchase_date: u64, - pub purchase_cost: i128, - pub current_value: i128, - pub warranty_expiry: u64, - pub stellar_token_id: BytesN<32>, + pub metadata_uri: String, + pub purchase_value: i128, + pub custom_attributes: Vec, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct AssetInfo { + pub id: BytesN<32>, + pub name: String, + pub category: String, pub owner: Address, + pub status: AssetStatus, } // Note: Contract methods implemented in lib.rs diff --git a/contracts/assetsup/src/error.rs b/contracts/assetsup/src/error.rs index 42fa4e3..40e3508 100644 --- a/contracts/assetsup/src/error.rs +++ b/contracts/assetsup/src/error.rs @@ -6,20 +6,19 @@ use soroban_sdk::{Env, contracterror, panic_with_error}; pub enum Error { AlreadyInitialized = 1, AdminNotFound = 2, - // Asset exist AssetAlreadyExists = 3, - //Asset not found AssetNotFound = 4, - // Branch already exists BranchAlreadyExists = 5, - // Branch not found BranchNotFound = 6, - // Subscription already exist SubscriptionAlreadyExists = 7, - // User not authorized Unauthorized = 8, - // Payment is not valid InvalidPayment = 9, + ContractPaused = 10, + ContractNotInitialized = 11, + InvalidAssetName = 12, + InvalidPurchaseValue = 13, + InvalidMetadataUri = 14, + InvalidOwnerAddress = 15, } pub fn handle_error(env: &Env, error: Error) -> ! { diff --git a/contracts/assetsup/src/lib.rs b/contracts/assetsup/src/lib.rs index a4a1881..910a34a 100644 --- a/contracts/assetsup/src/lib.rs +++ b/contracts/assetsup/src/lib.rs @@ -1,7 +1,7 @@ #![no_std] use crate::error::{Error, handle_error}; -use soroban_sdk::{Address, BytesN, Env, String, Vec, contract, contractimpl, contracttype}; +use soroban_sdk::{Address, BytesN, Env, String, Vec, contract, contractimpl, contracttype, symbol_short}; pub(crate) mod asset; pub(crate) mod audit; @@ -15,6 +15,32 @@ pub use types::*; #[derive(Clone, Debug, Eq, PartialEq)] pub enum DataKey { Admin, + Paused, + ContractVersion, + ContractMetadata, + AuthorizedRegistrar(Address), + TotalAssetCount, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ContractMetadata { + pub version: String, + pub name: String, + pub description: String, + pub created_at: u64, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Event { + AssetRegistered(Address, BytesN<32>, u64), + AssetTransferred(BytesN<32>, Address, Address, u64), + AssetUpdated(BytesN<32>, Address, u64), + AssetRetired(BytesN<32>, Address, u64), + AdminChanged(Address, Address, u64), + ContractPaused(Address, u64), + ContractUnpaused(Address, u64), } #[contract] @@ -28,7 +54,26 @@ impl AssetUpContract { if env.storage().persistent().has(&DataKey::Admin) { handle_error(&env, Error::AlreadyInitialized) } + + // Set admin env.storage().persistent().set(&DataKey::Admin, &admin); + + // Initialize contract state + env.storage().persistent().set(&DataKey::Paused, &false); + env.storage().persistent().set(&DataKey::TotalAssetCount, &0u64); + + // Set contract metadata + let metadata = ContractMetadata { + version: String::from_str(&env, "1.0.0"), + name: String::from_str(&env, "AssetUp Registry"), + description: String::from_str(&env, "Professional asset registry smart contract"), + created_at: env.ledger().timestamp(), + }; + env.storage().persistent().set(&DataKey::ContractMetadata, &metadata); + + // Add admin as first authorized registrar + env.storage().persistent().set(&DataKey::AuthorizedRegistrar(admin.clone()), &true); + Ok(()) } @@ -42,37 +87,118 @@ impl AssetUpContract { Ok(admin) } + pub fn is_paused(env: Env) -> Result { + Ok(env.storage().persistent().get(&DataKey::Paused).unwrap_or(false)) + } + + pub fn get_total_asset_count(env: Env) -> Result { + Ok(env.storage().persistent().get(&DataKey::TotalAssetCount).unwrap_or(0u64)) + } + + pub fn get_contract_metadata(env: Env) -> Result { + let metadata = env.storage().persistent().get(&DataKey::ContractMetadata); + match metadata { + Some(m) => Ok(m), + None => handle_error(&env, Error::ContractNotInitialized), + } + } + + pub fn is_authorized_registrar(env: Env, address: Address) -> Result { + Ok(env.storage().persistent().get(&DataKey::AuthorizedRegistrar(address)).unwrap_or(false)) + } + // Asset functions - pub fn register_asset(env: Env, asset: asset::Asset) -> Result<(), Error> { - asset.owner.require_auth(); + pub fn register_asset(env: Env, asset: asset::Asset, caller: Address) -> Result<(), Error> { + // Check if contract is paused + if Self::is_paused(env.clone())? { + return Err(Error::ContractPaused); + } - if asset.name.is_empty() { - panic!("Name cannot be empty"); + // Check if caller is authorized registrar + if !Self::is_authorized_registrar(env.clone(), caller.clone())? { + return Err(Error::Unauthorized); } + // Validate asset data + Self::validate_asset(&env, &asset)?; + let key = asset::DataKey::Asset(asset.id.clone()); let store = env.storage().persistent(); + + // Check if asset already exists if store.has(&key) { return Err(Error::AssetAlreadyExists); } + + // Store asset store.set(&key, &asset); - // Log the procurement action - audit::log_action( - &env, - &asset.id, - asset.owner, - ActionType::Procured, - String::from_str(&env, "Asset registered"), + // Update owner registry + let owner_key = asset::DataKey::OwnerRegistry(asset.owner.clone()); + let mut owner_assets: Vec> = store.get(&owner_key).unwrap_or_else(|| Vec::new(&env)); + owner_assets.push_back(asset.id.clone()); + store.set(&owner_key, &owner_assets); + + // Update total asset count + let mut total_count = Self::get_total_asset_count(env.clone())?; + total_count += 1; + env.storage().persistent().set(&DataKey::TotalAssetCount, &total_count); + + // Emit event + env.events().publish( + (symbol_short!("asset_reg"),), + (asset.owner, asset.id, env.ledger().timestamp()), ); + Ok(()) } - pub fn update_asset_status( + fn validate_asset(env: &Env, asset: &asset::Asset) -> Result<(), Error> { + // Validate asset name length (3-100 characters) + if asset.name.len() < 3 || asset.name.len() > 100 { + return Err(Error::InvalidAssetName); + } + + // Validate purchase value is non-negative + if asset.purchase_value < 0 { + return Err(Error::InvalidPurchaseValue); + } + + // Validate metadata URI format (basic check for IPFS hash format) + if !asset.metadata_uri.is_empty() && !Self::is_valid_metadata_uri(&asset.metadata_uri) { + return Err(Error::InvalidMetadataUri); + } + + // Validate owner address is not zero address + let zero_address = Address::from_str(&env, "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF"); + if asset.owner == zero_address { + return Err(Error::InvalidOwnerAddress); + } + + Ok(()) + } + + fn is_valid_metadata_uri(uri: &String) -> bool { + // For Soroban String, we'll use a simple length check and basic pattern matching + // In a real implementation, you might want to convert to bytes for more detailed validation + let uri_len = uri.len(); + // Basic validation: check for reasonable length and common prefixes + uri_len > 10 && (uri_len < 500) + } + + pub fn update_asset_metadata( env: Env, asset_id: BytesN<32>, - new_status: AssetStatus, + new_description: Option, + new_metadata_uri: Option, + new_custom_attributes: Option>, + caller: Address, ) -> Result<(), Error> { + // Check if contract is paused + if Self::is_paused(env.clone())? { + return Err(Error::ContractPaused); + } + let key = asset::DataKey::Asset(asset_id.clone()); let store = env.storage().persistent(); @@ -81,244 +207,253 @@ impl AssetUpContract { None => return Err(Error::AssetNotFound), }; - // Only asset owner can update status - asset.owner.require_auth(); + // Only asset owner or admin can update metadata + let admin = Self::get_admin(env.clone())?; + if caller != asset.owner && caller != admin { + return Err(Error::Unauthorized); + } + + // Update metadata if provided + if let Some(description) = new_description { + asset.description = description; + } - // Update status - asset.status = new_status.clone(); - store.set(&key, &asset); + if let Some(metadata_uri) = new_metadata_uri { + if !metadata_uri.is_empty() && !Self::is_valid_metadata_uri(&metadata_uri) { + return Err(Error::InvalidMetadataUri); + } + asset.metadata_uri = metadata_uri; + } - // Log appropriate audit action based on status - let (details, action_type) = match new_status { - AssetStatus::InMaintenance => ( - String::from_str(&env, "Asset in maintenance"), - ActionType::Maintained, - ), - AssetStatus::Disposed => ( - String::from_str(&env, "Asset disposed"), - ActionType::Disposed, - ), - _ => return Ok(()), // Don't log other status changes - }; + if let Some(custom_attributes) = new_custom_attributes { + asset.custom_attributes = custom_attributes; + } - audit::log_action(&env, &asset_id, asset.owner, action_type, details); + store.set(&key, &asset); - Ok(()) - } + // Emit event + env.events().publish( + (symbol_short!("asset_upd"),), + (asset_id, caller, env.ledger().timestamp()), + ); - pub fn get_asset(env: Env, asset_id: BytesN<32>) -> Result { - let key = asset::DataKey::Asset(asset_id); - let store = env.storage().persistent(); - match store.get::<_, asset::Asset>(&key) { - Some(a) => Ok(a), - None => Err(Error::AssetNotFound), - } + Ok(()) } - // Branch functions - pub fn create_branch( + pub fn transfer_asset_ownership( env: Env, - id: BytesN<32>, - name: String, - location: String, - admin: Address, + asset_id: BytesN<32>, + new_owner: Address, + caller: Address, ) -> Result<(), Error> { - let contract_admin = Self::get_admin(env.clone())?; - contract_admin.require_auth(); + // Check if contract is paused + if Self::is_paused(env.clone())? { + return Err(Error::ContractPaused); + } - if name.is_empty() { - panic!("Branch name cannot be empty"); + // Validate new owner is not zero address + let zero_address = Address::from_str(&env, "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF"); + if new_owner == zero_address { + return Err(Error::InvalidOwnerAddress); } - let key = branch::DataKey::Branch(id.clone()); + let key = asset::DataKey::Asset(asset_id.clone()); let store = env.storage().persistent(); - if store.has(&key) { - return Err(Error::BranchAlreadyExists); - } - let branch = branch::Branch { - id: id.clone(), - name, - location, - admin, + let mut asset = match store.get::<_, asset::Asset>(&key) { + Some(a) => a, + None => return Err(Error::AssetNotFound), }; - store.set(&key, &branch); + // Only current asset owner can transfer ownership + if caller != asset.owner { + return Err(Error::Unauthorized); + } - let asset_list_key = branch::DataKey::AssetList(id); - let empty_asset_list: Vec> = Vec::new(&env); - store.set(&asset_list_key, &empty_asset_list); + let old_owner = asset.owner.clone(); + + // Remove asset from old owner's registry + let old_owner_key = asset::DataKey::OwnerRegistry(old_owner.clone()); + let mut old_owner_assets: Vec> = store.get(&old_owner_key).unwrap_or_else(|| Vec::new(&env)); + if let Some(index) = old_owner_assets.iter().position(|x| x == asset_id) { + old_owner_assets.remove(index as u32); + } + store.set(&old_owner_key, &old_owner_assets); + + // Add asset to new owner's registry + let new_owner_key = asset::DataKey::OwnerRegistry(new_owner.clone()); + let mut new_owner_assets: Vec> = store.get(&new_owner_key).unwrap_or_else(|| Vec::new(&env)); + new_owner_assets.push_back(asset_id.clone()); + store.set(&new_owner_key, &new_owner_assets); + + // Update asset + asset.owner = new_owner.clone(); + asset.last_transfer_timestamp = env.ledger().timestamp(); + asset.status = AssetStatus::Transferred; + store.set(&key, &asset); + + // Emit event + env.events().publish( + (symbol_short!("asset_tx"),), + (asset_id, old_owner, new_owner, env.ledger().timestamp()), + ); Ok(()) } - pub fn add_asset_to_branch( - env: Env, - branch_id: BytesN<32>, - asset_id: BytesN<32>, - ) -> Result<(), Error> { - let store = env.storage().persistent(); - let branch_key = branch::DataKey::Branch(branch_id.clone()); - if !store.has(&branch_key) { - return Err(Error::BranchNotFound); + pub fn retire_asset(env: Env, asset_id: BytesN<32>, caller: Address) -> Result<(), Error> { + // Check if contract is paused + if Self::is_paused(env.clone())? { + return Err(Error::ContractPaused); } - let asset_key = asset::DataKey::Asset(asset_id.clone()); - if !store.has(&asset_key) { - return Err(Error::AssetNotFound); - } + let key = asset::DataKey::Asset(asset_id.clone()); + let store = env.storage().persistent(); - let asset_list_key = branch::DataKey::AssetList(branch_id); - let mut asset_list: Vec> = - store.get(&asset_list_key).unwrap_or_else(|| Vec::new(&env)); + let mut asset = match store.get::<_, asset::Asset>(&key) { + Some(a) => a, + None => return Err(Error::AssetNotFound), + }; - for existing_asset_id in asset_list.iter() { - if existing_asset_id == asset_id { - return Ok(()); - } + // Only asset owner or admin can retire asset + let admin = Self::get_admin(env.clone())?; + if caller != asset.owner && caller != admin { + return Err(Error::Unauthorized); } - asset_list.push_back(asset_id); - store.set(&asset_list_key, &asset_list); + asset.status = AssetStatus::Retired; + store.set(&key, &asset); + + // Emit event + env.events().publish( + (symbol_short!("asset_ret"),), + (asset_id, caller, env.ledger().timestamp()), + ); Ok(()) } - pub fn get_branch_assets(env: Env, branch_id: BytesN<32>) -> Result>, Error> { + pub fn get_asset(env: Env, asset_id: BytesN<32>) -> Result { + let key = asset::DataKey::Asset(asset_id); let store = env.storage().persistent(); - let branch_key = branch::DataKey::Branch(branch_id.clone()); - if !store.has(&branch_key) { - return Err(Error::BranchNotFound); + match store.get::<_, asset::Asset>(&key) { + Some(a) => Ok(a), + None => Err(Error::AssetNotFound), } + } - let asset_list_key = branch::DataKey::AssetList(branch_id); - match store.get(&asset_list_key) { - Some(asset_list) => Ok(asset_list), + pub fn get_assets_by_owner(env: Env, owner: Address) -> Result>, Error> { + let key = asset::DataKey::OwnerRegistry(owner); + let store = env.storage().persistent(); + match store.get(&key) { + Some(assets) => Ok(assets), None => Ok(Vec::new(&env)), } } - pub fn get_branch(env: Env, branch_id: BytesN<32>) -> Result { - let key = branch::DataKey::Branch(branch_id); + pub fn check_asset_exists(env: Env, asset_id: BytesN<32>) -> Result { + let key = asset::DataKey::Asset(asset_id); let store = env.storage().persistent(); - match store.get::<_, branch::Branch>(&key) { - Some(branch) => Ok(branch), - None => Err(Error::BranchNotFound), + Ok(store.has(&key)) + } + + pub fn get_asset_info(env: Env, asset_id: BytesN<32>) -> Result { + let asset = Self::get_asset(env.clone(), asset_id.clone())?; + Ok(asset::AssetInfo { + id: asset.id, + name: asset.name, + category: asset.category, + owner: asset.owner, + status: asset.status, + }) + } + + pub fn batch_get_asset_info(env: Env, asset_ids: Vec>) -> Result, Error> { + let mut results = Vec::new(&env); + for asset_id in asset_ids.iter() { + match Self::get_asset_info(env.clone(), asset_id.clone()) { + Ok(info) => results.push_back(info), + Err(_) => continue, // Skip assets that don't exist + } } + Ok(results) } - pub fn tokenize_asset( - env: Env, - asset_id: BytesN<32>, - token_id: BytesN<32>, - ) -> Result<(), Error> { - let admin_key = DataKey::Admin; - if !env.storage().persistent().has(&admin_key) { - handle_error(&env, Error::AdminNotFound) + // Admin functions + pub fn update_admin(env: Env, new_admin: Address) -> Result<(), Error> { + let current_admin = Self::get_admin(env.clone())?; + current_admin.require_auth(); + + // Validate new admin is not zero address + let zero_address = Address::from_str(&env, "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF"); + if new_admin == zero_address { + return Err(Error::InvalidOwnerAddress); } - let admin: Address = env.storage().persistent().get(&admin_key).unwrap(); - admin.require_auth(); - let key = asset::DataKey::Asset(asset_id.clone()); - let store = env.storage().persistent(); - let mut a: asset::Asset = match store.get(&key) { - Some(v) => v, - None => return Err(Error::AssetNotFound), - }; + let old_admin = current_admin.clone(); + env.storage().persistent().set(&DataKey::Admin, &new_admin); + + // Remove old admin from authorized registrars and add new admin + env.storage().persistent().set(&DataKey::AuthorizedRegistrar(old_admin.clone()), &false); + env.storage().persistent().set(&DataKey::AuthorizedRegistrar(new_admin.clone()), &true); + + // Emit event + env.events().publish( + (symbol_short!("admin_chg"),), + (old_admin, new_admin, env.ledger().timestamp()), + ); - a.stellar_token_id = token_id; - store.set(&key, &a); Ok(()) } - pub fn transfer_asset( - env: Env, - actor: Address, - asset_id: BytesN<32>, - new_branch_id: BytesN<32>, - ) -> Result<(), Error> { - actor.require_auth(); + pub fn add_authorized_registrar(env: Env, registrar: Address) -> Result<(), Error> { + let admin = Self::get_admin(env.clone())?; + admin.require_auth(); - let store = env.storage().persistent(); - let asset_key = asset::DataKey::Asset(asset_id.clone()); + env.storage().persistent().set(&DataKey::AuthorizedRegistrar(registrar), &true); + Ok(()) + } - let mut asset: asset::Asset = match store.get(&asset_key) { - Some(a) => a, - None => return Err(Error::AssetNotFound), - }; + pub fn remove_authorized_registrar(env: Env, registrar: Address) -> Result<(), Error> { + let admin = Self::get_admin(env.clone())?; + admin.require_auth(); - let contract_admin = Self::get_admin(env.clone())?; - if actor != contract_admin && actor != asset.owner { + // Cannot remove admin from authorized registrars + if registrar == admin { return Err(Error::Unauthorized); } - let old_branch_id = asset.branch_id.clone(); - - if old_branch_id == new_branch_id { - return Ok(()); - } - - let new_branch_key = branch::DataKey::Branch(new_branch_id.clone()); - if !store.has(&new_branch_key) { - return Err(Error::BranchNotFound); - } - - let old_asset_list_key = branch::DataKey::AssetList(old_branch_id); - let mut old_asset_list: Vec> = store.get(&old_asset_list_key).unwrap(); - if let Some(index) = old_asset_list.iter().position(|x| x == asset_id) { - old_asset_list.remove(index as u32); - } - store.set(&old_asset_list_key, &old_asset_list); + env.storage().persistent().set(&DataKey::AuthorizedRegistrar(registrar), &false); + Ok(()) + } - let new_asset_list_key = branch::DataKey::AssetList(new_branch_id.clone()); - let mut new_asset_list: Vec> = store - .get(&new_asset_list_key) - .unwrap_or_else(|| Vec::new(&env)); - new_asset_list.push_back(asset_id.clone()); - store.set(&new_asset_list_key, &new_asset_list); + pub fn pause_contract(env: Env) -> Result<(), Error> { + let admin = Self::get_admin(env.clone())?; + admin.require_auth(); - asset.branch_id = new_branch_id; - store.set(&asset_key, &asset); + env.storage().persistent().set(&DataKey::Paused, &true); - let note = String::from_str(&env, "Asset transferred"); - audit::log_action(&env, &asset_id, actor, ActionType::Transferred, note); + // Emit event + env.events().publish( + (symbol_short!("c_pause"),), + (admin, env.ledger().timestamp()), + ); Ok(()) } - pub fn log_action( - env: Env, - actor: Address, - asset_id: BytesN<32>, - action: ActionType, - note: String, - ) -> Result<(), Error> { - actor.require_auth(); - - let store = env.storage().persistent(); - let asset_key = asset::DataKey::Asset(asset_id.clone()); - - let asset: asset::Asset = match store.get(&asset_key) { - Some(a) => a, - None => return Err(Error::AssetNotFound), - }; + pub fn unpause_contract(env: Env) -> Result<(), Error> { + let admin = Self::get_admin(env.clone())?; + admin.require_auth(); - let contract_admin = Self::get_admin(env.clone())?; - if actor != contract_admin && actor != asset.owner { - return Err(Error::Unauthorized); - } + env.storage().persistent().set(&DataKey::Paused, &false); - audit::log_action(&env, &asset_id, actor, action, note); + // Emit event + env.events().publish( + (symbol_short!("c_unpause"),), + (admin, env.ledger().timestamp()), + ); Ok(()) } - - pub fn get_asset_audit_logs( - env: Env, - asset_id: BytesN<32>, - ) -> Result, Error> { - Ok(audit::get_asset_log(&env, &asset_id)) - } } - -mod tests; diff --git a/contracts/assetsup/src/tests/access_control.rs b/contracts/assetsup/src/tests/access_control.rs deleted file mode 100644 index 3a6a452..0000000 --- a/contracts/assetsup/src/tests/access_control.rs +++ /dev/null @@ -1,201 +0,0 @@ -#![cfg(test)] -use crate::asset::Asset; -use crate::types::{ActionType, AssetStatus, AssetType}; -use crate::{AssetUpContract, AssetUpContractClient}; -use soroban_sdk::{Address, BytesN, Env, String, testutils::Address as _}; - -extern crate std; - -/// Setup test environment with contract and addresses -fn setup_test_environment() -> (Env, AssetUpContractClient<'static>, Address) { - let env = Env::default(); - env.mock_all_auths(); - - let contract_id = env.register(AssetUpContract, ()); - let client = AssetUpContractClient::new(&env, &contract_id); - - let admin = Address::generate(&env); - - (env, client, admin) -} - -#[test] -fn test_global_admin_can_create_branch() { - let (env, client, admin) = setup_test_environment(); - client.initialize(&admin); - - // Admin should be able to create branch - let branch_id = soroban_sdk::BytesN::from_array(&env, &[1u8; 32]); - let branch_name = soroban_sdk::String::from_str(&env, "Test Branch"); - let branch_location = soroban_sdk::String::from_str(&env, "Test Location"); - let branch_admin = Address::generate(&env); - - client.create_branch(&branch_id, &branch_name, &branch_location, &branch_admin); - - // Verify branch was created - let branch = client.get_branch(&branch_id); - assert_eq!(branch.id, branch_id); - assert_eq!(branch.name, branch_name); - assert_eq!(branch.location, branch_location); - assert_eq!(branch.admin, branch_admin); -} - -#[test] -fn test_global_admin_can_tokenize_asset() { - let (env, client, admin) = setup_test_environment(); - client.initialize(&admin); - - // Register an asset - let asset_id = BytesN::from_array(&env, &[1u8; 32]); - let asset_owner = Address::generate(&env); - let branch_id = BytesN::from_array(&env, &[2u8; 32]); - let asset = Asset { - id: asset_id.clone(), - name: String::from_str(&env, "Test Asset"), - asset_type: AssetType::Physical, - category: String::from_str(&env, "Test Category"), - branch_id, - department_id: 1, - status: AssetStatus::Active, - purchase_date: 1000, - purchase_cost: 1000, - current_value: 1000, - warranty_expiry: 2000, - stellar_token_id: BytesN::from_array(&env, &[0u8; 32]), - owner: asset_owner.clone(), - }; - - client.register_asset(&asset); - - // Admin should be able to tokenize asset - let token_id = BytesN::from_array(&env, &[2u8; 32]); - client.tokenize_asset(&asset_id, &token_id); - - // Verify asset was tokenized - let updated_asset = client.get_asset(&asset_id); - assert_eq!(updated_asset.stellar_token_id, token_id); -} - -#[test] -fn test_asset_owner_can_log_audit_action() { - let (env, client, admin) = setup_test_environment(); - client.initialize(&admin); - - // Register an asset - let asset_id = BytesN::from_array(&env, &[1u8; 32]); - let asset_owner = Address::generate(&env); - let branch_id = BytesN::from_array(&env, &[2u8; 32]); - let asset = Asset { - id: asset_id.clone(), - name: String::from_str(&env, "Test Asset"), - asset_type: AssetType::Physical, - category: String::from_str(&env, "Test Category"), - branch_id, - department_id: 1, - status: AssetStatus::Active, - purchase_date: 1000, - purchase_cost: 1000, - current_value: 1000, - warranty_expiry: 2000, - stellar_token_id: BytesN::from_array(&env, &[0u8; 32]), - owner: asset_owner.clone(), - }; - - client.register_asset(&asset); - - // Asset owner should be able to log audit action - let details = String::from_str(&env, "Asset registered"); - - // Verify audit log was created - let logs = client.get_asset_audit_logs(&asset_id); - assert_eq!(logs.len(), 1); - assert_eq!(logs.get(0).unwrap().action, ActionType::Procured); - assert_eq!(logs.get(0).unwrap().note, details); -} - -#[test] -fn test_global_admin_can_log_audit_action() { - let (env, client, admin) = setup_test_environment(); - client.initialize(&admin); - - // Register an asset - let asset_id = BytesN::from_array(&env, &[1u8; 32]); - let asset_owner = Address::generate(&env); - let branch_id = BytesN::from_array(&env, &[2u8; 32]); - let asset = Asset { - id: asset_id.clone(), - name: String::from_str(&env, "Test Asset"), - asset_type: AssetType::Physical, - category: String::from_str(&env, "Test Category"), - branch_id, - department_id: 1, - status: AssetStatus::Active, - purchase_date: 1000, - purchase_cost: 1000, - current_value: 1000, - warranty_expiry: 2000, - stellar_token_id: BytesN::from_array(&env, &[0u8; 32]), - owner: asset_owner.clone(), - }; - - client.register_asset(&asset); - - // Global admin should be able to log audit action - let details = String::from_str(&env, "Asset registered"); - - // Verify audit log was created - let logs = client.get_asset_audit_logs(&asset_id); - assert_eq!(logs.len(), 1); - assert_eq!(logs.get(0).unwrap().action, ActionType::Procured); - assert_eq!(logs.get(0).unwrap().note, details); - assert_eq!(logs.get(0).unwrap().actor, asset_owner); -} - -#[test] -fn test_multiple_audit_logs_for_asset() { - let (env, client, admin) = setup_test_environment(); - client.initialize(&admin); - - // Register an asset - let asset_id = BytesN::from_array(&env, &[1u8; 32]); - let asset_owner = Address::generate(&env); - let branch_id = BytesN::from_array(&env, &[2u8; 32]); - let asset = Asset { - id: asset_id.clone(), - name: String::from_str(&env, "Test Asset"), - asset_type: AssetType::Physical, - category: String::from_str(&env, "Test Category"), - branch_id, - department_id: 1, - status: AssetStatus::Active, - purchase_date: 1000, - purchase_cost: 1000, - current_value: 1000, - warranty_expiry: 2000, - stellar_token_id: BytesN::from_array(&env, &[0u8; 32]), - owner: asset_owner.clone(), - }; - - client.register_asset(&asset); - - // Log multiple audit actions - let action1 = ActionType::Procured; - let details1 = String::from_str(&env, "Asset registered"); - - let action2 = ActionType::Inspected; - let details2 = String::from_str(&env, "Safety inspection"); - client.log_action(&admin, &asset_id, &action2, &details2); - - // Verify both audit logs were created - let logs = client.get_asset_audit_logs(&asset_id); - assert_eq!(logs.len(), 2); - - // Check that both actions are present - let log1 = logs.get(0).unwrap(); - assert_eq!(log1.action, action1); - assert_eq!(log1.note, details1); - - let log2 = logs.get(1).unwrap(); - assert_eq!(log2.action, action2); - assert_eq!(log2.note, details2); -} diff --git a/contracts/assetsup/src/tests/asset.rs b/contracts/assetsup/src/tests/asset.rs deleted file mode 100644 index acee51a..0000000 --- a/contracts/assetsup/src/tests/asset.rs +++ /dev/null @@ -1,141 +0,0 @@ -#![cfg(test)] - -extern crate std; - -use soroban_sdk::testutils::{Address as _, BytesN as _}; - -use soroban_sdk::{Address, BytesN, Env, String}; - -use crate::{ - asset::Asset, - types::{ActionType, AssetStatus, AssetType}, -}; - -use super::initialize::setup_test_environment; - -fn make_bytes32(env: &Env, seed: u32) -> BytesN<32> { - let mut arr = [0u8; 32]; - // Simple deterministic fill - for (i, item) in arr.iter_mut().enumerate() { - *item = ((seed as usize + i) % 256) as u8; - } - BytesN::from_array(env, &arr) -} - -#[test] -fn test_register_and_get_asset_success() { - let (env, client, _admin) = setup_test_environment(); - let owner = Address::generate(&env); - - let id = make_bytes32(&env, 1); - let token = make_bytes32(&env, 2); - let branch_id = make_bytes32(&env, 10); - - let name = String::from_str(&env, "Laptop A"); - let category = String::from_str(&env, "Electronics"); - - let asset = Asset { - id: id.clone(), - name: name.clone(), - asset_type: AssetType::Digital, - category: category.clone(), - branch_id: branch_id.clone(), - department_id: 20, - status: AssetStatus::Active, - purchase_date: 1_725_000_000, - purchase_cost: 120_000, - current_value: 100_000, - warranty_expiry: 1_800_000_000, - stellar_token_id: token.clone(), - owner: owner.clone(), - }; - - let res = client.try_register_asset(&asset); - assert!(res.is_ok()); - - let got = client.try_get_asset(&id).unwrap().unwrap(); - - assert_eq!(got.id, id); - assert_eq!(got.name, name); - assert_eq!(got.asset_type, AssetType::Digital); - assert_eq!(got.category, category); - assert_eq!(got.branch_id, branch_id); - assert_eq!(got.department_id, 20); - assert_eq!(got.status, AssetStatus::Active); - assert_eq!(got.purchase_date, 1_725_000_000); - assert_eq!(got.purchase_cost, 120_000); - assert_eq!(got.current_value, 100_000); - assert_eq!(got.warranty_expiry, 1_800_000_000); - assert_eq!(got.stellar_token_id, token); - assert_eq!(got.owner, owner); -} - -#[test] -#[should_panic] -fn test_register_asset_duplicate() { - let (env, client, _admin) = setup_test_environment(); - let owner = Address::generate(&env); - - let id = make_bytes32(&env, 3); - let token = make_bytes32(&env, 4); - let branch_id = make_bytes32(&env, 1); - - let name = String::from_str(&env, "Office Chair"); - let category = String::from_str(&env, "Furniture"); - - let asset = Asset { - id: id.clone(), - name: name.clone(), - asset_type: AssetType::Physical, - category: category.clone(), - branch_id: branch_id.clone(), - department_id: 2, - status: AssetStatus::Active, - purchase_date: 1_700_000_000, - purchase_cost: 15_000, - current_value: 12_000, - warranty_expiry: 1_750_000_000, - stellar_token_id: token.clone(), - owner: owner.clone(), - }; - - // First registration should succeed - client.register_asset(&asset); - - // Second registration with same ID should panic (Err propagated) - client.register_asset(&asset); -} - -#[test] -fn test_update_status_creates_audit_log() { - let (env, client, _admin) = setup_test_environment(); - let owner = Address::generate(&env); - - // Create and register asset first - let asset = Asset { - id: BytesN::random(&env), - name: String::from_str(&env, "Test Asset"), - asset_type: AssetType::Physical, - category: String::from_str(&env, "Test Category"), - branch_id: BytesN::random(&env), - department_id: 1, - status: AssetStatus::Active, - purchase_date: 12345, - purchase_cost: 1000, - current_value: 900, - warranty_expiry: 67890, - stellar_token_id: BytesN::random(&env), - owner: owner.clone(), - }; - - client.register_asset(&asset); - - // Update to Maintained status - client.update_asset_status(&asset.id, &AssetStatus::InMaintenance); - - // Verify audit logs - let logs = client.get_asset_audit_logs(&asset.id); - assert_eq!(logs.len(), 2); // Procurement + Maintenance - assert_eq!(logs.get(1).unwrap().action, ActionType::Maintained); - assert_eq!(logs.get(1).unwrap().actor, owner); -} diff --git a/contracts/assetsup/src/tests/branch.rs b/contracts/assetsup/src/tests/branch.rs deleted file mode 100644 index 08c041c..0000000 --- a/contracts/assetsup/src/tests/branch.rs +++ /dev/null @@ -1,301 +0,0 @@ -#![cfg(test)] - -extern crate std; - -use super::initialize::setup_test_environment; -use crate::asset::Asset; -use crate::types::{AssetStatus, AssetType}; -use soroban_sdk::{Address, BytesN, String, testutils::Address as _}; - -#[test] -fn test_create_branch() { - let (env, client, admin) = setup_test_environment(); - client.initialize(&admin); - - let branch_id = BytesN::from_array(&env, &[1u8; 32]); - let branch_name = String::from_str(&env, "Main Branch"); - let branch_location = String::from_str(&env, "New York"); - let branch_admin = Address::generate(&env); - - // Create branch - client.create_branch(&branch_id, &branch_name, &branch_location, &branch_admin); - - // Verify branch was created - let branch = client.get_branch(&branch_id); - assert_eq!(branch.id, branch_id); - assert_eq!(branch.name, branch_name); - assert_eq!(branch.location, branch_location); - assert_eq!(branch.admin, branch_admin); - - // Verify empty asset list was initialized - let assets = client.get_branch_assets(&branch_id); - assert_eq!(assets.len(), 0); -} - -#[test] -#[should_panic] -fn test_create_branch_duplicate() { - let (env, client, admin) = setup_test_environment(); - client.initialize(&admin); - - let branch_id = BytesN::from_array(&env, &[1u8; 32]); - let branch_name = String::from_str(&env, "Main Branch"); - let branch_location = String::from_str(&env, "New York"); - let branch_admin = Address::generate(&env); - - // Create branch first time - client.create_branch(&branch_id, &branch_name, &branch_location, &branch_admin); - - // Try to create branch with same ID - should panic - client.create_branch(&branch_id, &branch_name, &branch_location, &branch_admin); -} - -#[test] -#[should_panic] -fn test_create_branch_empty_name() { - let (env, client, admin) = setup_test_environment(); - client.initialize(&admin); - - let branch_id = BytesN::from_array(&env, &[1u8; 32]); - let branch_name = String::from_str(&env, ""); // Empty name - let branch_location = String::from_str(&env, "New York"); - let branch_admin = Address::generate(&env); - - // Should panic on empty name - client.create_branch(&branch_id, &branch_name, &branch_location, &branch_admin); -} - -#[test] -fn test_add_asset_to_branch() { - let (env, client, admin) = setup_test_environment(); - client.initialize(&admin); - - // Create branch - let branch_id = BytesN::from_array(&env, &[1u8; 32]); - let branch_name = String::from_str(&env, "Main Branch"); - let branch_location = String::from_str(&env, "New York"); - let branch_admin = Address::generate(&env); - client.create_branch(&branch_id, &branch_name, &branch_location, &branch_admin); - - // Create asset - let asset_id = BytesN::from_array(&env, &[2u8; 32]); - let asset = Asset { - id: asset_id.clone(), - name: String::from_str(&env, "Test Asset"), - asset_type: AssetType::Physical, - category: String::from_str(&env, "Computer"), - branch_id: branch_id.clone(), - department_id: 1, - status: AssetStatus::Active, - purchase_date: 1234567890, - purchase_cost: 1000, - current_value: 800, - warranty_expiry: 1234567890, - stellar_token_id: BytesN::from_array(&env, &[3u8; 32]), - owner: Address::generate(&env), - }; - client.register_asset(&asset); - - // Add asset to branch - client.add_asset_to_branch(&branch_id, &asset_id); - - // Verify asset is in branch - let assets = client.get_branch_assets(&branch_id); - assert_eq!(assets.len(), 1); - assert_eq!(assets.get(0).unwrap(), asset_id); -} - -#[test] -#[should_panic] -fn test_add_asset_to_nonexistent_branch() { - let (env, client, admin) = setup_test_environment(); - client.initialize(&admin); - - let branch_id = BytesN::from_array(&env, &[1u8; 32]); - let asset_id = BytesN::from_array(&env, &[2u8; 32]); - - // Try to add asset to non-existent branch - should panic - client.add_asset_to_branch(&branch_id, &asset_id); -} - -#[test] -#[should_panic] -fn test_add_nonexistent_asset_to_branch() { - let (env, client, admin) = setup_test_environment(); - client.initialize(&admin); - - // Create branch - let branch_id = BytesN::from_array(&env, &[1u8; 32]); - let branch_name = String::from_str(&env, "Main Branch"); - let branch_location = String::from_str(&env, "New York"); - let branch_admin = Address::generate(&env); - client.create_branch(&branch_id, &branch_name, &branch_location, &branch_admin); - - let asset_id = BytesN::from_array(&env, &[2u8; 32]); - - // Try to add non-existent asset to branch - should panic - client.add_asset_to_branch(&branch_id, &asset_id); -} - -#[test] -fn test_add_duplicate_asset_to_branch() { - let (env, client, admin) = setup_test_environment(); - client.initialize(&admin); - - // Create branch - let branch_id = BytesN::from_array(&env, &[1u8; 32]); - let branch_name = String::from_str(&env, "Main Branch"); - let branch_location = String::from_str(&env, "New York"); - let branch_admin = Address::generate(&env); - client.create_branch(&branch_id, &branch_name, &branch_location, &branch_admin); - - // Create asset - let asset_id = BytesN::from_array(&env, &[2u8; 32]); - let asset = Asset { - id: asset_id.clone(), - name: String::from_str(&env, "Test Asset"), - asset_type: AssetType::Physical, - category: String::from_str(&env, "Computer"), - branch_id: branch_id.clone(), - department_id: 1, - status: AssetStatus::Active, - purchase_date: 1234567890, - purchase_cost: 1000, - current_value: 800, - warranty_expiry: 1234567890, - stellar_token_id: BytesN::from_array(&env, &[3u8; 32]), - owner: Address::generate(&env), - }; - client.register_asset(&asset); - - // Add asset to branch first time - client.add_asset_to_branch(&branch_id, &asset_id); - - // Add same asset again (should not panic) - client.add_asset_to_branch(&branch_id, &asset_id); - - // Verify asset is still only once in the list - let assets = client.get_branch_assets(&branch_id); - assert_eq!(assets.len(), 1); - assert_eq!(assets.get(0).unwrap(), asset_id); -} - -#[test] -fn test_get_branch_assets_multiple() { - let (env, client, admin) = setup_test_environment(); - client.initialize(&admin); - - // Create branch - let branch_id = BytesN::from_array(&env, &[1u8; 32]); - let branch_name = String::from_str(&env, "Main Branch"); - let branch_location = String::from_str(&env, "New York"); - let branch_admin = Address::generate(&env); - client.create_branch(&branch_id, &branch_name, &branch_location, &branch_admin); - - // Create multiple assets - let asset1_id = BytesN::from_array(&env, &[2u8; 32]); - let asset2_id = BytesN::from_array(&env, &[3u8; 32]); - let asset3_id = BytesN::from_array(&env, &[4u8; 32]); - - let asset1 = Asset { - id: asset1_id.clone(), - name: String::from_str(&env, "Asset 1"), - asset_type: AssetType::Physical, - category: String::from_str(&env, "Computer"), - branch_id: branch_id.clone(), - department_id: 1, - status: AssetStatus::Active, - purchase_date: 1234567890, - purchase_cost: 1000, - current_value: 800, - warranty_expiry: 1234567890, - stellar_token_id: BytesN::from_array(&env, &[5u8; 32]), - owner: Address::generate(&env), - }; - - let asset2 = Asset { - id: asset2_id.clone(), - name: String::from_str(&env, "Asset 2"), - asset_type: AssetType::Physical, - category: String::from_str(&env, "Desk"), - branch_id: branch_id.clone(), - department_id: 1, - status: AssetStatus::Active, - purchase_date: 1234567890, - purchase_cost: 500, - current_value: 400, - warranty_expiry: 1234567890, - stellar_token_id: BytesN::from_array(&env, &[6u8; 32]), - owner: Address::generate(&env), - }; - - let asset3 = Asset { - id: asset3_id.clone(), - name: String::from_str(&env, "Asset 3"), - asset_type: AssetType::Physical, - category: String::from_str(&env, "Car"), - branch_id: branch_id.clone(), - department_id: 1, - status: AssetStatus::Active, - purchase_date: 1234567890, - purchase_cost: 20000, - current_value: 15000, - warranty_expiry: 1234567890, - stellar_token_id: BytesN::from_array(&env, &[7u8; 32]), - owner: Address::generate(&env), - }; - - client.register_asset(&asset1); - client.register_asset(&asset2); - client.register_asset(&asset3); - - // Add assets to branch - client.add_asset_to_branch(&branch_id, &asset1_id); - client.add_asset_to_branch(&branch_id, &asset2_id); - client.add_asset_to_branch(&branch_id, &asset3_id); - - // Get branch assets - let assets = client.get_branch_assets(&branch_id); - assert_eq!(assets.len(), 3); - - // Verify all assets are present - let mut found_asset1 = false; - let mut found_asset2 = false; - let mut found_asset3 = false; - - for i in 0..assets.len() { - let asset_id = assets.get(i).unwrap(); - if asset_id == asset1_id { - found_asset1 = true; - } else if asset_id == asset2_id { - found_asset2 = true; - } else if asset_id == asset3_id { - found_asset3 = true; - } - } - assert!(found_asset1 && found_asset2 && found_asset3); -} - -#[test] -#[should_panic] -fn test_get_branch_assets_nonexistent_branch() { - let (env, client, admin) = setup_test_environment(); - client.initialize(&admin); - - let branch_id = BytesN::from_array(&env, &[1u8; 32]); - - // Try to get assets for non-existent branch - should panic - client.get_branch_assets(&branch_id); -} - -#[test] -#[should_panic] -fn test_get_branch_nonexistent() { - let (env, client, admin) = setup_test_environment(); - client.initialize(&admin); - - let branch_id = BytesN::from_array(&env, &[1u8; 32]); - - // Try to get non-existent branch - should panic - client.get_branch(&branch_id); -} diff --git a/contracts/assetsup/src/tests/initialize.rs b/contracts/assetsup/src/tests/initialize.rs deleted file mode 100644 index 05e596d..0000000 --- a/contracts/assetsup/src/tests/initialize.rs +++ /dev/null @@ -1,43 +0,0 @@ -#![cfg(test)] - -extern crate std; - -use crate::{AssetUpContract, AssetUpContractClient}; -use soroban_sdk::{Address, Env, testutils::Address as _}; - -/// Setup test environment with contract and addresses -pub fn setup_test_environment() -> (Env, AssetUpContractClient<'static>, Address) { - let env = Env::default(); - env.mock_all_auths(); - - let contract_id = env.register(AssetUpContract, ()); - let client = AssetUpContractClient::new(&env, &contract_id); - - let admin = Address::generate(&env); - - (env, client, admin) -} - -#[test] -fn test_initialize() { - let (_env, client, admin) = setup_test_environment(); - client.initialize(&admin); - let saved_admin = client.get_admin(); - - assert_eq!(admin, saved_admin); -} - -#[test] -#[should_panic(expected = "Error(Contract, #1)")] -fn test_initialize_panic() { - let (_env, client, admin) = setup_test_environment(); - client.initialize(&admin); - client.initialize(&admin); -} - -#[test] -#[should_panic(expected = "Error(Contract, #2)")] -fn test_admin_doesnt_exist() { - let (_env, client, _) = setup_test_environment(); - client.get_admin(); -} diff --git a/contracts/assetsup/src/tests/mod.rs b/contracts/assetsup/src/tests/mod.rs deleted file mode 100644 index b96caf2..0000000 --- a/contracts/assetsup/src/tests/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod access_control; -mod asset; -mod branch; -mod initialize; -mod tokenize; -mod transfer; -mod types; diff --git a/contracts/assetsup/src/tests/tokenize.rs b/contracts/assetsup/src/tests/tokenize.rs deleted file mode 100644 index 9684c11..0000000 --- a/contracts/assetsup/src/tests/tokenize.rs +++ /dev/null @@ -1,131 +0,0 @@ -#[test] -fn test_mint_tokens_v1() { - let env = Env::default(); - let tokenizer = Address::random(&env); - - let asset_id = 200u64; - let symbol = "AST200".to_string(); - let total_supply = BigInt::from_i128(&env, 500); - let decimals = 2u32; - let name = "Mint Test Asset".to_string(); - let description = "Testing minting".to_string(); - let asset_type = AssetType::Digital; - - // Tokenize asset first - let tokenized_asset = TokenizeContract::tokenize( - env.clone(), - asset_id, - symbol, - total_supply.clone(), - decimals, - name, - description, - asset_type, - tokenizer.clone(), - ); - - // Mint additional tokens - let mint_amount = BigInt::from_i128(&env, 200); - let updated_asset = TokenizeContract::mint_tokens(env.clone(), asset_id, mint_amount.clone(), tokenizer.clone()); - - // Verify total supply increased - assert_eq!(updated_asset.total_supply, &total_supply + &mint_amount); - - // Verify tokenizer's ownership updated - let ownership: OwnershipRecord = env.storage().get((b"ownership", asset_id, &tokenizer)).unwrap().unwrap(); - assert_eq!(ownership.balance, &total_supply + &mint_amount); -} - -#[test] -fn test_burn_tokens_v1() { - let env = Env::default(); - let tokenizer = Address::random(&env); - - let asset_id = 300u64; - let symbol = "AST300".to_string(); - let total_supply = BigInt::from_i128(&env, 1000); - let decimals = 2u32; - let name = "Burn Test Asset".to_string(); - let description = "Testing burning".to_string(); - let asset_type = AssetType::Digital; - - // Tokenize asset first - let tokenized_asset = TokenizeContract::tokenize( - env.clone(), - asset_id, - symbol, - total_supply.clone(), - decimals, - name, - description, - asset_type, - tokenizer.clone(), - ); - - // Burn some tokens - let burn_amount = BigInt::from_i128(&env, 400); - let updated_asset = TokenizeContract::burn_tokens(env.clone(), asset_id, burn_amount.clone(), tokenizer.clone()); - - // Verify total supply decreased - assert_eq!(updated_asset.total_supply, &total_supply - &burn_amount); - - // Verify tokenizer's ownership updated - let ownership: OwnershipRecord = env.storage().get((b"ownership", asset_id, &tokenizer)).unwrap().unwrap(); - assert_eq!(ownership.balance, &total_supply - &burn_amount); -} - -#[test] -#[should_panic(expected = "Unauthorized: only tokenizer can mint")] -fn test_mint_unauthorized() { - let env = Env::default(); - let tokenizer = Address::random(&env); - let attacker = Address::random(&env); - - let asset_id = 400u64; - let total_supply = BigInt::from_i128(&env, 500); - - // Tokenize asset first - TokenizeContract::tokenize( - env.clone(), - asset_id, - "AST400".to_string(), - total_supply.clone(), - 2, - "Unauthorized Mint".to_string(), - "Test".to_string(), - AssetType::Digital, - tokenizer.clone(), - ); - - // Attempt to mint from unauthorized address - let mint_amount = BigInt::from_i128(&env, 100); - TokenizeContract::mint_tokens(env, asset_id, mint_amount, attacker); -} - -#[test] -#[should_panic(expected = "Unauthorized: only tokenizer can burn")] -fn test_burn_unauthorized() { - let env = Env::default(); - let tokenizer = Address::random(&env); - let attacker = Address::random(&env); - - let asset_id = 500u64; - let total_supply = BigInt::from_i128(&env, 500); - - // Tokenize asset first - TokenizeContract::tokenize( - env.clone(), - asset_id, - "AST500".to_string(), - total_supply.clone(), - 2, - "Unauthorized Burn".to_string(), - "Test".to_string(), - AssetType::Digital, - tokenizer.clone(), - ); - - // Attempt to burn from unauthorized address - let burn_amount = BigInt::from_i128(&env, 100); - TokenizeContract::burn_tokens(env, asset_id, burn_amount, attacker); -} diff --git a/contracts/assetsup/src/tests/transfer.rs b/contracts/assetsup/src/tests/transfer.rs deleted file mode 100644 index 2205303..0000000 --- a/contracts/assetsup/src/tests/transfer.rs +++ /dev/null @@ -1,292 +0,0 @@ -#![cfg(test)] - -extern crate std; - -use soroban_sdk::{ - Address, BytesN, Env, String, - testutils::{Address as _, Ledger as _}, -}; - -use crate::{ - asset::Asset, - types::{ActionType, AssetStatus, AssetType}, -}; - -use super::initialize::setup_test_environment; - -fn make_bytes32(env: &Env, seed: u32) -> BytesN<32> { - let mut arr = [0u8; 32]; - for (i, item) in arr.iter_mut().enumerate() { - *item = ((seed as usize + i) % 256) as u8; - } - BytesN::from_array(env, &arr) -} - -#[test] -fn test_transfer_asset_by_owner() { - let (env, client, admin) = setup_test_environment(); - client.initialize(&admin); - - let owner = Address::generate(&env); - let asset_id = make_bytes32(&env, 1); - let initial_branch_id = make_bytes32(&env, 10); - let new_branch_id = make_bytes32(&env, 20); - - // Create branches - client.create_branch( - &initial_branch_id, - &String::from_str(&env, "Initial Branch"), - &String::from_str(&env, "Location A"), - &Address::generate(&env), - ); - client.create_branch( - &new_branch_id, - &String::from_str(&env, "New Branch"), - &String::from_str(&env, "Location B"), - &Address::generate(&env), - ); - - let asset = Asset { - id: asset_id.clone(), - name: String::from_str(&env, "Test Asset"), - asset_type: AssetType::Physical, - category: String::from_str(&env, "Equipment"), - branch_id: initial_branch_id.clone(), - department_id: 1, - status: AssetStatus::Active, - purchase_date: 0, - purchase_cost: 1000, - current_value: 800, - warranty_expiry: 0, - stellar_token_id: make_bytes32(&env, 2), - owner: owner.clone(), - }; - client.register_asset(&asset); - client.add_asset_to_branch(&initial_branch_id, &asset_id); - - assert_eq!(client.get_branch_assets(&initial_branch_id).len(), 1); - assert_eq!(client.get_branch_assets(&new_branch_id).len(), 0); - - env.ledger().with_mut(|li| { - li.timestamp = 12345; - }); - - client.transfer_asset(&owner, &asset_id, &new_branch_id); - - let updated_asset = client.get_asset(&asset_id); - assert_eq!(updated_asset.branch_id, new_branch_id); - - assert_eq!(client.get_branch_assets(&initial_branch_id).len(), 0); - assert_eq!(client.get_branch_assets(&new_branch_id).len(), 1); - assert_eq!( - client.get_branch_assets(&new_branch_id).get(0).unwrap(), - asset_id - ); - - let log = client.get_asset_audit_logs(&asset_id); - assert_eq!(log.len(), 2); - let entry = log.get(0).unwrap(); - assert_eq!(entry.actor, owner); - assert_eq!(entry.action, ActionType::Procured); -} - -#[test] -fn test_transfer_asset_by_admin() { - let (env, client, admin) = setup_test_environment(); - client.initialize(&admin); - - let owner = Address::generate(&env); - let asset_id = make_bytes32(&env, 3); - let initial_branch_id = make_bytes32(&env, 30); - let new_branch_id = make_bytes32(&env, 40); - - // Create branches - client.create_branch( - &initial_branch_id, - &String::from_str(&env, "Initial Branch"), - &String::from_str(&env, "Location A"), - &Address::generate(&env), - ); - client.create_branch( - &new_branch_id, - &String::from_str(&env, "New Branch"), - &String::from_str(&env, "Location B"), - &Address::generate(&env), - ); - - let asset = Asset { - id: asset_id.clone(), - name: String::from_str(&env, "Admin Transfer"), - asset_type: AssetType::Digital, - category: String::from_str(&env, "Software"), - branch_id: initial_branch_id.clone(), - department_id: 2, - status: AssetStatus::Active, - purchase_date: 0, - purchase_cost: 500, - current_value: 500, - warranty_expiry: 0, - stellar_token_id: make_bytes32(&env, 4), - owner: owner.clone(), - }; - client.register_asset(&asset); - client.add_asset_to_branch(&initial_branch_id, &asset_id); - - client.transfer_asset(&admin, &asset_id, &new_branch_id); - - let updated_asset = client.get_asset(&asset_id); - assert_eq!(updated_asset.branch_id, new_branch_id); - - assert_eq!(client.get_branch_assets(&initial_branch_id).len(), 0); - assert_eq!(client.get_branch_assets(&new_branch_id).len(), 1); -} - -#[test] -#[should_panic(expected = "Error(Contract, #8)")] -fn test_transfer_asset_unauthorized() { - let (env, client, admin) = setup_test_environment(); - client.initialize(&admin); - - let owner = Address::generate(&env); - let unauthorized_actor = Address::generate(&env); - let asset_id = make_bytes32(&env, 5); - let branch_id = make_bytes32(&env, 1); - let new_branch_id = make_bytes32(&env, 2); - - client.create_branch( - &branch_id, - &String::from_str(&env, "Branch 1"), - &String::from_str(&env, "Location"), - &Address::generate(&env), - ); - client.create_branch( - &new_branch_id, - &String::from_str(&env, "Branch 2"), - &String::from_str(&env, "Location"), - &Address::generate(&env), - ); - - let asset = Asset { - id: asset_id.clone(), - name: String::from_str(&env, "Unauthorized Test"), - asset_type: AssetType::Physical, - category: String::from_str(&env, "Tool"), - branch_id: branch_id.clone(), - department_id: 1, - status: AssetStatus::Active, - purchase_date: 0, - purchase_cost: 100, - current_value: 100, - warranty_expiry: 0, - stellar_token_id: make_bytes32(&env, 6), - owner: owner.clone(), - }; - client.register_asset(&asset); - client.add_asset_to_branch(&branch_id, &asset_id); - - client.transfer_asset(&unauthorized_actor, &asset_id, &new_branch_id); -} - -#[test] -fn test_transfer_to_same_branch() { - let (env, client, admin) = setup_test_environment(); - client.initialize(&admin); - - let owner = Address::generate(&env); - let asset_id = make_bytes32(&env, 7); - let initial_branch_id = make_bytes32(&env, 50); - - client.create_branch( - &initial_branch_id, - &String::from_str(&env, "Branch"), - &String::from_str(&env, "Location"), - &Address::generate(&env), - ); - - let asset = Asset { - id: asset_id.clone(), - name: String::from_str(&env, "Same Branch Transfer"), - asset_type: AssetType::Physical, - category: String::from_str(&env, "Furniture"), - branch_id: initial_branch_id.clone(), - department_id: 3, - status: AssetStatus::Active, - purchase_date: 0, - purchase_cost: 200, - current_value: 150, - warranty_expiry: 0, - stellar_token_id: make_bytes32(&env, 8), - owner: owner.clone(), - }; - client.register_asset(&asset); - client.add_asset_to_branch(&initial_branch_id, &asset_id); - - // No transfer should happen, no error - client.transfer_asset(&owner, &asset_id, &initial_branch_id); - - let updated_asset = client.get_asset(&asset_id); - assert_eq!(updated_asset.branch_id, initial_branch_id); - - assert_eq!(client.get_branch_assets(&initial_branch_id).len(), 1); - - // Only the registration log should exist - let log = client.get_asset_audit_logs(&asset_id); - assert_eq!(log.len(), 1); -} - -#[test] -#[should_panic(expected = "Error(Contract, #4)")] -fn test_transfer_nonexistent_asset() { - let (env, client, admin) = setup_test_environment(); - client.initialize(&admin); - - let non_existent_asset_id = make_bytes32(&env, 99); - let branch_id = make_bytes32(&env, 100); - client.create_branch( - &branch_id, - &String::from_str(&env, "Branch"), - &String::from_str(&env, "Location"), - &Address::generate(&env), - ); - - client.transfer_asset(&admin, &non_existent_asset_id, &branch_id); -} - -#[test] -#[should_panic(expected = "Error(Contract, #6)")] -fn test_transfer_to_nonexistent_branch() { - let (env, client, admin) = setup_test_environment(); - client.initialize(&admin); - - let owner = Address::generate(&env); - let asset_id = make_bytes32(&env, 1); - let initial_branch_id = make_bytes32(&env, 10); - let non_existent_branch_id = make_bytes32(&env, 99); - - client.create_branch( - &initial_branch_id, - &String::from_str(&env, "Initial"), - &String::from_str(&env, "Location"), - &Address::generate(&env), - ); - - let asset = Asset { - id: asset_id.clone(), - name: String::from_str(&env, "Test Asset"), - asset_type: AssetType::Physical, - category: String::from_str(&env, "Equipment"), - branch_id: initial_branch_id.clone(), - department_id: 1, - status: AssetStatus::Active, - purchase_date: 0, - purchase_cost: 1000, - current_value: 800, - warranty_expiry: 0, - stellar_token_id: make_bytes32(&env, 2), - owner: owner.clone(), - }; - client.register_asset(&asset); - client.add_asset_to_branch(&initial_branch_id, &asset_id); - - client.transfer_asset(&owner, &asset_id, &non_existent_branch_id); -} diff --git a/contracts/assetsup/src/tests/types.rs b/contracts/assetsup/src/tests/types.rs deleted file mode 100644 index 712dc49..0000000 --- a/contracts/assetsup/src/tests/types.rs +++ /dev/null @@ -1,125 +0,0 @@ -#![cfg(test)] - -use crate::types::*; - -#[test] -fn test_asset_type_variants() { - // Test that all AssetType variants can be created - let physical = AssetType::Physical; - let digital = AssetType::Digital; - - // Test equality - assert_eq!(physical, AssetType::Physical); - assert_eq!(digital, AssetType::Digital); - assert_ne!(physical, digital); - - // Test cloning - let physical_clone = physical.clone(); - assert_eq!(physical, physical_clone); -} - -#[test] -fn test_asset_status_variants() { - // Test that all AssetStatus variants can be created - let active = AssetStatus::Active; - let in_maintenance = AssetStatus::InMaintenance; - let disposed = AssetStatus::Disposed; - - // Test equality - assert_eq!(active, AssetStatus::Active); - assert_eq!(in_maintenance, AssetStatus::InMaintenance); - assert_eq!(disposed, AssetStatus::Disposed); - - // Test that they are different - assert_ne!(active, in_maintenance); - assert_ne!(active, disposed); - assert_ne!(in_maintenance, disposed); -} - -#[test] -fn test_action_type_variants() { - // Test that all ActionType variants can be created - let procured = ActionType::Procured; - let transferred = ActionType::Transferred; - let maintained = ActionType::Maintained; - let disposed = ActionType::Disposed; - let checked_in = ActionType::CheckedIn; - let checked_out = ActionType::CheckedOut; - - // Test equality - assert_eq!(procured, ActionType::Procured); - assert_eq!(transferred, ActionType::Transferred); - assert_eq!(maintained, ActionType::Maintained); - assert_eq!(disposed, ActionType::Disposed); - assert_eq!(checked_in, ActionType::CheckedIn); - assert_eq!(checked_out, ActionType::CheckedOut); - - // Test that they are different - assert_ne!(procured, transferred); - assert_ne!(checked_in, checked_out); -} - -#[test] -fn test_plan_type_variants() { - // Test that all PlanType variants can be created - let basic = PlanType::Basic; - let pro = PlanType::Pro; - let enterprise = PlanType::Enterprise; - - // Test equality - assert_eq!(basic, PlanType::Basic); - assert_eq!(pro, PlanType::Pro); - assert_eq!(enterprise, PlanType::Enterprise); - - // Test that they are different - assert_ne!(basic, pro); - assert_ne!(pro, enterprise); - assert_ne!(basic, enterprise); -} - -#[test] -fn test_subscription_status_variants() { - // Test that all SubscriptionStatus variants can be created - let active = SubscriptionStatus::Active; - let expired = SubscriptionStatus::Expired; - let cancelled = SubscriptionStatus::Cancelled; - - // Test equality - assert_eq!(active, SubscriptionStatus::Active); - assert_eq!(expired, SubscriptionStatus::Expired); - assert_eq!(cancelled, SubscriptionStatus::Cancelled); - - // Test that they are different - assert_ne!(active, expired); - assert_ne!(active, cancelled); - assert_ne!(expired, cancelled); -} - -#[test] -fn test_types_can_be_used_in_functions() { - // Test that types can be used as function parameters and return values - fn get_asset_type() -> AssetType { - AssetType::Physical - } - - fn process_action(action: ActionType) -> bool { - match action { - ActionType::Procured => true, - ActionType::Transferred => true, - ActionType::Maintained => true, - ActionType::Disposed => false, - ActionType::CheckedIn => true, - ActionType::CheckedOut => true, - ActionType::Inspected => true, - } - } - - let asset_type = get_asset_type(); - assert_eq!(asset_type, AssetType::Physical); - - let result = process_action(ActionType::Procured); - assert!(result); - - let result = process_action(ActionType::Disposed); - assert!(!result); -} diff --git a/contracts/assetsup/src/types.rs b/contracts/assetsup/src/types.rs index 70f9d79..4fb864e 100644 --- a/contracts/assetsup/src/types.rs +++ b/contracts/assetsup/src/types.rs @@ -1,5 +1,5 @@ #![allow(clippy::upper_case_acronyms)] -use soroban_sdk::contracttype; +use soroban_sdk::{String, contracttype}; /// Represents the fundamental type of asset being managed /// Distinguishes between physical and digital assets for different handling requirements @@ -16,8 +16,8 @@ pub enum AssetType { #[derive(Clone, Debug, Eq, PartialEq)] pub enum AssetStatus { Active, - InMaintenance, - Disposed, + Transferred, + Retired, } /// Represents different types of actions that can be performed on assets @@ -54,6 +54,15 @@ pub enum SubscriptionStatus { Cancelled, } +/// Represents custom attributes for assets (key-value pairs) +/// Used for storing additional metadata about assets +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct CustomAttribute { + pub key: String, + pub value: String, +} + // ===================== // Tokenization / Fractional Ownership Types (V1) // =====================