diff --git a/contracts/token-factory/Cargo.toml b/contracts/token-factory/Cargo.toml
index 12381a80..12305da4 100644
--- a/contracts/token-factory/Cargo.toml
+++ b/contracts/token-factory/Cargo.toml
@@ -1,19 +1,18 @@
-[package]
-name = "token-factory"
-version = "0.1.0"
-edition = "2021"
-
-[lib]
-crate-type = ["cdylib"]
-
-[dependencies]
-soroban-sdk = "21.0.0"
-soroban-token-sdk = { version = "21.0.0" }
-proptest = "1.4"
-
-[dev-dependencies]
-soroban-sdk = { version = "21.0.0", features = ["testutils"] }
-soroban-token-sdk = { version = "21.0.0", features = ["testutils"] }
-
-[features]
-testutils = ["soroban-sdk/testutils", "soroban-token-sdk/testutils"]
\ No newline at end of file
+[package]
+name = "token-factory"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib"]
+
+[dependencies]
+soroban-sdk = "21.0.0"
+soroban-token-sdk = { version = "21.0.0" }
+proptest = "1.4"
+
+[dev-dependencies]
+soroban-sdk = { version = "21.0.0", features = ["testutils"] }
+
+[features]
+testutils = ["soroban-sdk/testutils"]
\ No newline at end of file
diff --git a/contracts/token-factory/src/lib.rs b/contracts/token-factory/src/lib.rs
index 2303f5d4..ad68ec47 100644
--- a/contracts/token-factory/src/lib.rs
+++ b/contracts/token-factory/src/lib.rs
@@ -1,322 +1,322 @@
-#![no_std]
-
-use soroban_sdk::{contract, contractimpl, contracttype, Address, Env, String, Vec, vec, symbol_short, token};
-use soroban_token_sdk::TokenClient;
-
-#[contracttype]
-#[derive(Clone)]
-pub struct TokenInfo {
- pub name: String,
- pub symbol: String,
- pub decimals: u32,
- pub creator: Address,
- pub created_at: u64,
-}
-
-#[contracttype]
-#[derive(Clone)]
-pub struct FactoryState {
- pub admin: Address,
- pub paused: bool,
- pub treasury: Address,
- pub base_fee: i128,
- pub metadata_fee: i128,
- pub token_count: u32,
-}
-
-#[contract]
-pub struct TokenFactory;
-
-#[contractimpl]
-impl TokenFactory {
- pub fn initialize(
- env: Env,
- admin: Address,
- treasury: Address,
- base_fee: i128,
- metadata_fee: i128,
- ) -> Result<(), Error> {
- if env.storage().instance().has(&symbol_short!("init")) {
- return Err(Error::AlreadyInitialized);
- }
-
- // FIX 1: Added `paused: false` to the FactoryState initializer
- let state = FactoryState {
- admin: admin.clone(),
- paused: false,
- treasury,
- base_fee,
- metadata_fee,
- token_count: 0,
- };
-
- env.storage().instance().set(&symbol_short!("state"), &state);
- env.storage().instance().set(&symbol_short!("init"), &true);
-
- env.events().publish((symbol_short!("init"),), (admin,));
-
- Ok(())
- }
-
- // FIX 2: Replaced DataKey::State with symbol_short!("state") to match the rest of the codebase
- fn require_not_paused(env: &Env) -> Result<(), Error> {
- let state: FactoryState = env.storage().instance().get(&symbol_short!("state")).unwrap();
- if state.paused {
- return Err(Error::ContractPaused);
- }
- Ok(())
- }
-
- pub fn create_token(
- env: Env,
- creator: Address,
- name: String,
- symbol: String,
- decimals: u32,
- initial_supply: i128,
- fee_payment: i128,
- ) -> Result
{
- // FIX 3: Changed require_not_paused(&env) to Self::require_not_paused(&env)
- Self::require_not_paused(&env)?;
- creator.require_auth();
-
- let state: FactoryState = env.storage().instance().get(&symbol_short!("state")).unwrap();
-
- if fee_payment < state.base_fee {
- return Err(Error::InsufficientFee);
- }
-
- // Transfer fee to treasury
- token::StellarAssetClient::new(&env, &env.current_contract_address()).transfer(
- &creator,
- &state.treasury,
- &fee_payment,
- );
-
- // Deploy token using soroban-token-sdk
- let token_address = env.deployer().deploy_token(
- &name,
- &symbol,
- &decimals,
- &creator,
- &initial_supply,
- );
-
- // Store token info
- let token_info = TokenInfo {
- name,
- symbol,
- decimals,
- creator: creator.clone(),
- created_at: env.ledger().timestamp(),
- };
-
- let mut token_count = state.token_count;
- token_count += 1;
-
- env.storage().instance().set(&token_count, &token_info);
- env.storage().instance().set(&symbol_short!("state"), &FactoryState {
- token_count,
- ..state
- });
-
- // Append token index to creator's list
- let creator_key = (symbol_short!("cr_tokens"), creator.clone());
- let mut creator_tokens: Vec = env
- .storage()
- .instance()
- .get(&creator_key)
- .unwrap_or_else(|| vec![&env]);
- creator_tokens.push_back(token_count);
- env.storage().instance().set(&creator_key, &creator_tokens);
-
- env.events().publish((symbol_short!("token_created"),), (token_address.clone(), creator));
-
- Ok(token_address)
- }
-
- pub fn set_metadata(
- env: Env,
- token_address: Address,
- admin: Address,
- metadata_uri: String,
- fee_payment: i128,
- ) -> Result<(), Error> {
- // FIX 3: Changed require_not_paused(&env) to Self::require_not_paused(&env)
- Self::require_not_paused(&env)?;
- admin.require_auth();
-
- let state: FactoryState = env.storage().instance().get(&symbol_short!("state")).unwrap();
-
- if fee_payment < state.metadata_fee {
- return Err(Error::InsufficientFee);
- }
-
- // Transfer fee
- token::StellarAssetClient::new(&env, &env.current_contract_address()).transfer(
- &admin,
- &state.treasury,
- &fee_payment,
- );
-
- env.storage().instance().set(&(&token_address, symbol_short!("metadata")), &metadata_uri);
-
- env.events().publish((symbol_short!("metadata_set"),), (token_address, metadata_uri));
-
- Ok(())
- }
-
- pub fn mint_tokens(
- env: Env,
- token_address: Address,
- admin: Address,
- to: Address,
- amount: i128,
- fee_payment: i128,
- ) -> Result<(), Error> {
- // FIX 3: Changed require_not_paused(&env) to Self::require_not_paused(&env)
- Self::require_not_paused(&env)?;
- admin.require_auth();
-
- let state: FactoryState = env.storage().instance().get(&symbol_short!("state")).unwrap();
-
- if fee_payment < state.base_fee {
- return Err(Error::InsufficientFee);
- }
-
- // Transfer fee
- token::StellarAssetClient::new(&env, &env.current_contract_address()).transfer(
- &admin,
- &state.treasury,
- &fee_payment,
- );
-
- // Mint tokens
- TokenClient::new(&env, &token_address).mint(&admin, &to, &amount);
-
- env.events().publish((symbol_short!("tokens_minted"),), (token_address, to, amount));
-
- Ok(())
- }
-
- pub fn burn(
- env: Env,
- token_address: Address,
- from: Address,
- amount: i128,
- ) -> Result<(), Error> {
- // NOTE: burn intentionally has NO require_not_paused check
- from.require_auth();
-
- if amount <= 0 {
- return Err(Error::InvalidBurnAmount);
- }
-
- TokenClient::new(&env, &token_address).burn(&from, &amount);
-
- env.events().publish((symbol_short!("tokens_burned"),), (token_address, from, amount));
-
- Ok(())
- }
-
- // FIX 2: Replaced DataKey::State with symbol_short!("state") throughout pause/unpause
- pub fn pause(env: Env, admin: Address) -> Result<(), Error> {
- admin.require_auth();
- let mut state: FactoryState = env.storage().instance().get(&symbol_short!("state")).unwrap();
-
- if state.admin != admin {
- return Err(Error::Unauthorized);
- }
-
- state.paused = true;
- env.storage().instance().set(&symbol_short!("state"), &state);
- Ok(())
- }
-
- pub fn unpause(env: Env, admin: Address) -> Result<(), Error> {
- admin.require_auth();
- let mut state: FactoryState = env.storage().instance().get(&symbol_short!("state")).unwrap();
-
- if state.admin != admin {
- return Err(Error::Unauthorized);
- }
-
- state.paused = false;
- env.storage().instance().set(&symbol_short!("state"), &state);
- Ok(())
- }
-
- pub fn update_fees(
- env: Env,
- admin: Address,
- base_fee: Option,
- metadata_fee: Option,
- ) -> Result<(), Error> {
- admin.require_auth();
-
- let mut state: FactoryState = env.storage().instance().get(&symbol_short!("state")).unwrap();
-
- if admin != state.admin {
- return Err(Error::Unauthorized);
- }
-
- if let Some(fee) = base_fee {
- state.base_fee = fee;
- }
- if let Some(fee) = metadata_fee {
- state.metadata_fee = fee;
- }
-
- env.storage().instance().set(&symbol_short!("state"), &state);
-
- env.events().publish((symbol_short!("fees_updated"),), (base_fee, metadata_fee));
-
- Ok(())
- }
-
- pub fn get_state(env: Env) -> FactoryState {
- env.storage().instance().get(&symbol_short!("state")).unwrap()
- }
-
- pub fn get_base_fee(env: Env) -> i128 {
- Self::get_state(env).base_fee
- }
-
- pub fn get_metadata_fee(env: Env) -> i128 {
- Self::get_state(env).metadata_fee
- }
-
- pub fn get_token_info(env: Env, index: u32) -> Result {
- match env.storage().instance().get(&index) {
- Some(info) => Ok(info),
- None => Err(Error::TokenNotFound),
- }
- }
-
- pub fn get_tokens_by_creator(env: Env, creator: Address) -> Vec {
- let creator_key = (symbol_short!("cr_tokens"), creator);
- env.storage()
- .instance()
- .get(&creator_key)
- .unwrap_or_else(|| vec![&env])
- }
-}
-
-#[contracttype]
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub enum Error {
- InsufficientFee = 1,
- Unauthorized = 2,
- InvalidParameters = 3,
- TokenNotFound = 4,
- MetadataAlreadySet = 5,
- AlreadyInitialized = 6,
- BurnAmountExceedsBalance = 7,
- BurnNotEnabled = 8,
- InvalidBurnAmount = 9,
- // FIX 4: Replaced X with 10
- ContractPaused = 10,
-}
-
-#[cfg(test)]
-mod test;
+#![no_std]
+
+use soroban_sdk::{contract, contractimpl, contracttype, Address, Env, String, Vec, vec, symbol_short, token};
+use soroban_token_sdk::TokenClient;
+
+#[contracttype]
+#[derive(Clone)]
+pub struct TokenInfo {
+ pub name: String,
+ pub symbol: String,
+ pub decimals: u32,
+ pub creator: Address,
+ pub created_at: u64,
+}
+
+#[contracttype]
+#[derive(Clone)]
+pub struct FactoryState {
+ pub admin: Address,
+ pub paused: bool,
+ pub treasury: Address,
+ pub base_fee: i128,
+ pub metadata_fee: i128,
+ pub token_count: u32,
+}
+
+#[contract]
+pub struct TokenFactory;
+
+#[contractimpl]
+impl TokenFactory {
+ pub fn initialize(
+ env: Env,
+ admin: Address,
+ treasury: Address,
+ base_fee: i128,
+ metadata_fee: i128,
+ ) -> Result<(), Error> {
+ if env.storage().instance().has(&symbol_short!("init")) {
+ return Err(Error::AlreadyInitialized);
+ }
+
+ // FIX 1: Added `paused: false` to the FactoryState initializer
+ let state = FactoryState {
+ admin: admin.clone(),
+ paused: false,
+ treasury,
+ base_fee,
+ metadata_fee,
+ token_count: 0,
+ };
+
+ env.storage().instance().set(&symbol_short!("state"), &state);
+ env.storage().instance().set(&symbol_short!("init"), &true);
+
+ env.events().publish((symbol_short!("init"),), (admin,));
+
+ Ok(())
+ }
+
+ // FIX 2: Replaced DataKey::State with symbol_short!("state") to match the rest of the codebase
+ fn require_not_paused(env: &Env) -> Result<(), Error> {
+ let state: FactoryState = env.storage().instance().get(&symbol_short!("state")).unwrap();
+ if state.paused {
+ return Err(Error::ContractPaused);
+ }
+ Ok(())
+ }
+
+ pub fn create_token(
+ env: Env,
+ creator: Address,
+ name: String,
+ symbol: String,
+ decimals: u32,
+ initial_supply: i128,
+ fee_payment: i128,
+ ) -> Result {
+ // FIX 3: Changed require_not_paused(&env) to Self::require_not_paused(&env)
+ Self::require_not_paused(&env)?;
+ creator.require_auth();
+
+ let state: FactoryState = env.storage().instance().get(&symbol_short!("state")).unwrap();
+
+ if fee_payment < state.base_fee {
+ return Err(Error::InsufficientFee);
+ }
+
+ // Transfer fee to treasury
+ token::StellarAssetClient::new(&env, &env.current_contract_address()).transfer(
+ &creator,
+ &state.treasury,
+ &fee_payment,
+ );
+
+ // Deploy token using soroban-token-sdk
+ let token_address = env.deployer().deploy_token(
+ &name,
+ &symbol,
+ &decimals,
+ &creator,
+ &initial_supply,
+ );
+
+ // Store token info
+ let token_info = TokenInfo {
+ name,
+ symbol,
+ decimals,
+ creator: creator.clone(),
+ created_at: env.ledger().timestamp(),
+ };
+
+ let mut token_count = state.token_count;
+ token_count += 1;
+
+ env.storage().instance().set(&token_count, &token_info);
+ env.storage().instance().set(&symbol_short!("state"), &FactoryState {
+ token_count,
+ ..state
+ });
+
+ // Append token index to creator's list
+ let creator_key = (symbol_short!("cr_tokens"), creator.clone());
+ let mut creator_tokens: Vec = env
+ .storage()
+ .instance()
+ .get(&creator_key)
+ .unwrap_or_else(|| vec![&env]);
+ creator_tokens.push_back(token_count);
+ env.storage().instance().set(&creator_key, &creator_tokens);
+
+ env.events().publish((symbol_short!("token_created"),), (token_address.clone(), creator));
+
+ Ok(token_address)
+ }
+
+ pub fn set_metadata(
+ env: Env,
+ token_address: Address,
+ admin: Address,
+ metadata_uri: String,
+ fee_payment: i128,
+ ) -> Result<(), Error> {
+ // FIX 3: Changed require_not_paused(&env) to Self::require_not_paused(&env)
+ Self::require_not_paused(&env)?;
+ admin.require_auth();
+
+ let state: FactoryState = env.storage().instance().get(&symbol_short!("state")).unwrap();
+
+ if fee_payment < state.metadata_fee {
+ return Err(Error::InsufficientFee);
+ }
+
+ // Transfer fee
+ token::StellarAssetClient::new(&env, &env.current_contract_address()).transfer(
+ &admin,
+ &state.treasury,
+ &fee_payment,
+ );
+
+ env.storage().instance().set(&(&token_address, symbol_short!("metadata")), &metadata_uri);
+
+ env.events().publish((symbol_short!("metadata_set"),), (token_address, metadata_uri));
+
+ Ok(())
+ }
+
+ pub fn mint_tokens(
+ env: Env,
+ token_address: Address,
+ admin: Address,
+ to: Address,
+ amount: i128,
+ fee_payment: i128,
+ ) -> Result<(), Error> {
+ // FIX 3: Changed require_not_paused(&env) to Self::require_not_paused(&env)
+ Self::require_not_paused(&env)?;
+ admin.require_auth();
+
+ let state: FactoryState = env.storage().instance().get(&symbol_short!("state")).unwrap();
+
+ if fee_payment < state.base_fee {
+ return Err(Error::InsufficientFee);
+ }
+
+ // Transfer fee
+ token::StellarAssetClient::new(&env, &env.current_contract_address()).transfer(
+ &admin,
+ &state.treasury,
+ &fee_payment,
+ );
+
+ // Mint tokens
+ TokenClient::new(&env, &token_address).mint(&admin, &to, &amount);
+
+ env.events().publish((symbol_short!("tokens_minted"),), (token_address, to, amount));
+
+ Ok(())
+ }
+
+ pub fn burn(
+ env: Env,
+ token_address: Address,
+ from: Address,
+ amount: i128,
+ ) -> Result<(), Error> {
+ // NOTE: burn intentionally has NO require_not_paused check
+ from.require_auth();
+
+ if amount <= 0 {
+ return Err(Error::InvalidBurnAmount);
+ }
+
+ TokenClient::new(&env, &token_address).burn(&from, &amount);
+
+ env.events().publish((symbol_short!("tokens_burned"),), (token_address, from, amount));
+
+ Ok(())
+ }
+
+ // FIX 2: Replaced DataKey::State with symbol_short!("state") throughout pause/unpause
+ pub fn pause(env: Env, admin: Address) -> Result<(), Error> {
+ admin.require_auth();
+ let mut state: FactoryState = env.storage().instance().get(&symbol_short!("state")).unwrap();
+
+ if state.admin != admin {
+ return Err(Error::Unauthorized);
+ }
+
+ state.paused = true;
+ env.storage().instance().set(&symbol_short!("state"), &state);
+ Ok(())
+ }
+
+ pub fn unpause(env: Env, admin: Address) -> Result<(), Error> {
+ admin.require_auth();
+ let mut state: FactoryState = env.storage().instance().get(&symbol_short!("state")).unwrap();
+
+ if state.admin != admin {
+ return Err(Error::Unauthorized);
+ }
+
+ state.paused = false;
+ env.storage().instance().set(&symbol_short!("state"), &state);
+ Ok(())
+ }
+
+ pub fn update_fees(
+ env: Env,
+ admin: Address,
+ base_fee: Option,
+ metadata_fee: Option,
+ ) -> Result<(), Error> {
+ admin.require_auth();
+
+ let mut state: FactoryState = env.storage().instance().get(&symbol_short!("state")).unwrap();
+
+ if admin != state.admin {
+ return Err(Error::Unauthorized);
+ }
+
+ if let Some(fee) = base_fee {
+ state.base_fee = fee;
+ }
+ if let Some(fee) = metadata_fee {
+ state.metadata_fee = fee;
+ }
+
+ env.storage().instance().set(&symbol_short!("state"), &state);
+
+ env.events().publish((symbol_short!("fees_updated"),), (base_fee, metadata_fee));
+
+ Ok(())
+ }
+
+ pub fn get_state(env: Env) -> FactoryState {
+ env.storage().instance().get(&symbol_short!("state")).unwrap()
+ }
+
+ pub fn get_base_fee(env: Env) -> i128 {
+ Self::get_state(env).base_fee
+ }
+
+ pub fn get_metadata_fee(env: Env) -> i128 {
+ Self::get_state(env).metadata_fee
+ }
+
+ pub fn get_token_info(env: Env, index: u32) -> Result {
+ match env.storage().instance().get(&index) {
+ Some(info) => Ok(info),
+ None => Err(Error::TokenNotFound),
+ }
+ }
+
+ pub fn get_tokens_by_creator(env: Env, creator: Address) -> Vec {
+ let creator_key = (symbol_short!("cr_tokens"), creator);
+ env.storage()
+ .instance()
+ .get(&creator_key)
+ .unwrap_or_else(|| vec![&env])
+ }
+}
+
+#[contracttype]
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum Error {
+ InsufficientFee = 1,
+ Unauthorized = 2,
+ InvalidParameters = 3,
+ TokenNotFound = 4,
+ MetadataAlreadySet = 5,
+ AlreadyInitialized = 6,
+ BurnAmountExceedsBalance = 7,
+ BurnNotEnabled = 8,
+ InvalidBurnAmount = 9,
+ // FIX 4: Replaced X with 10
+ ContractPaused = 10,
+}
+
+#[cfg(test)]
+mod test;
diff --git a/contracts/token-factory/src/test.rs b/contracts/token-factory/src/test.rs
index 018ddee2..19c23fdc 100644
--- a/contracts/token-factory/src/test.rs
+++ b/contracts/token-factory/src/test.rs
@@ -1,256 +1,256 @@
-#![cfg(test)]
-
-use super::*;
-use soroban_sdk::{
- testutils::{Address as _, AuthorizedFunction, AuthorizedInvocation},
- Address, Env, String,
-};
-
-// ── helpers ──────────────────────────────────────────────────────────────────
-
-fn setup_env() -> (Env, TokenFactoryClient<'static>, Address, Address) {
- let env = Env::default();
- env.mock_all_auths();
-
- let contract_id = env.register_contract(None, TokenFactory);
- let client = TokenFactoryClient::new(&env, &contract_id);
-
- let admin = Address::generate(&env);
- let treasury = Address::generate(&env);
-
- client.initialize(&admin, &treasury, &1000, &500);
-
- (env, client, admin, treasury)
-}
-
-// ── pause / unpause ───────────────────────────────────────────────────────────
-
-#[test]
-fn test_initial_state_is_not_paused() {
- let (_env, client, _admin, _treasury) = setup_env();
- let state = client.get_state();
- assert!(!state.paused);
-}
-
-#[test]
-fn test_admin_can_pause() {
- let (_env, client, admin, _treasury) = setup_env();
- client.pause(&admin);
- let state = client.get_state();
- assert!(state.paused);
-}
-
-#[test]
-fn test_admin_can_unpause() {
- let (_env, client, admin, _treasury) = setup_env();
- client.pause(&admin);
- client.unpause(&admin);
- let state = client.get_state();
- assert!(!state.paused);
-}
-
-#[test]
-fn test_non_admin_cannot_pause() {
- let (env, client, _admin, _treasury) = setup_env();
- let stranger = Address::generate(&env);
-
- let result = client.try_pause(&stranger);
- assert_eq!(result, Err(Ok(Error::Unauthorized)));
-}
-
-#[test]
-fn test_non_admin_cannot_unpause() {
- let (env, client, admin, _treasury) = setup_env();
- let stranger = Address::generate(&env);
-
- client.pause(&admin);
- let result = client.try_unpause(&stranger);
- assert_eq!(result, Err(Ok(Error::Unauthorized)));
-}
-
-// ── paused blocks create_token, mint_tokens, set_metadata ────────────────────
-
-#[test]
-fn test_create_token_blocked_when_paused() {
- let (env, client, admin, _treasury) = setup_env();
- client.pause(&admin);
-
- let creator = Address::generate(&env);
- let result = client.try_create_token(
- &creator,
- &String::from_str(&env, "MyToken"),
- &String::from_str(&env, "MTK"),
- &7,
- &1_000_000,
- &1000,
- );
-
- assert_eq!(result, Err(Ok(Error::ContractPaused)));
-}
-
-#[test]
-fn test_mint_tokens_blocked_when_paused() {
- let (env, client, admin, _treasury) = setup_env();
- client.pause(&admin);
-
- let token_address = Address::generate(&env);
- let recipient = Address::generate(&env);
-
- let result = client.try_mint_tokens(
- &token_address,
- &admin,
- &recipient,
- &500,
- &1000,
- );
-
- assert_eq!(result, Err(Ok(Error::ContractPaused)));
-}
-
-#[test]
-fn test_set_metadata_blocked_when_paused() {
- let (env, client, admin, _treasury) = setup_env();
- client.pause(&admin);
-
- let token_address = Address::generate(&env);
-
- let result = client.try_set_metadata(
- &token_address,
- &admin,
- &String::from_str(&env, "https://example.com/meta.json"),
- &500,
- );
-
- assert_eq!(result, Err(Ok(Error::ContractPaused)));
-}
-
-// ── unpause restores functionality ───────────────────────────────────────────
-
-#[test]
-fn test_create_token_works_after_unpause() {
- // This test just verifies unpause lifts the block.
- // create_token will still fail due to fee transfer in test env,
- // but the error should NOT be ContractPaused.
- let (env, client, admin, _treasury) = setup_env();
-
- client.pause(&admin);
- client.unpause(&admin);
-
- let creator = Address::generate(&env);
- let result = client.try_create_token(
- &creator,
- &String::from_str(&env, "MyToken"),
- &String::from_str(&env, "MTK"),
- &7,
- &1_000_000,
- &1000,
- );
-
- // Should NOT be ContractPaused — any other error is fine here
- assert_ne!(result, Err(Ok(Error::ContractPaused)));
-}
-
-// ── burn is NOT blocked by pause ─────────────────────────────────────────────
-
-#[test]
-fn test_burn_not_blocked_when_paused() {
- let (env, client, admin, _treasury) = setup_env();
- client.pause(&admin);
-
- let token_address = Address::generate(&env);
- let burner = Address::generate(&env);
-
- // burn will fail because the token isn't real in this unit test,
- // but the error must NOT be ContractPaused
- let result = client.try_burn(&token_address, &burner, &100);
- assert_ne!(result, Err(Ok(Error::ContractPaused)));
-}
-
-// ── transfer_admin ────────────────────────────────────────────────────────────
-
-#[test]
-fn test_admin_can_transfer_ownership() {
- let (env, client, admin, _treasury) = setup_env();
- let new_admin = Address::generate(&env);
-
- client.transfer_admin(&admin, &new_admin);
-
- let state = client.get_state();
- assert_eq!(state.admin, new_admin);
-}
-
-#[test]
-fn test_old_admin_loses_privileges_after_transfer() {
- let (env, client, admin, _treasury) = setup_env();
- let new_admin = Address::generate(&env);
-
- client.transfer_admin(&admin, &new_admin);
-
- // old admin can no longer pause
- let result = client.try_pause(&admin);
- assert_eq!(result, Err(Ok(Error::Unauthorized)));
-}
-
-#[test]
-fn test_non_admin_cannot_transfer_admin() {
- let (env, client, _admin, _treasury) = setup_env();
- let stranger = Address::generate(&env);
- let new_admin = Address::generate(&env);
-
- let result = client.try_transfer_admin(&stranger, &new_admin);
- assert_eq!(result, Err(Ok(Error::Unauthorized)));
-}
-
-#[test]
-fn test_transfer_admin_to_same_address_fails() {
- let (_env, client, admin, _treasury) = setup_env();
-
- let result = client.try_transfer_admin(&admin, &admin);
- assert_eq!(result, Err(Ok(Error::InvalidParameters)));
-}
-
-// ── get_tokens_by_creator ─────────────────────────────────────────────────────
-
-#[test]
-fn test_get_tokens_by_creator_returns_empty_for_unknown_address() {
- let (env, client, _admin, _treasury) = setup_env();
- let stranger = Address::generate(&env);
-
- let indices = client.get_tokens_by_creator(&stranger);
- assert_eq!(indices.len(), 0);
-}
-
-#[test]
-fn test_get_tokens_by_creator_returns_correct_indices() {
- let (env, client, _admin, _treasury) = setup_env();
- let creator = Address::generate(&env);
-
- // create_token will fail at the fee-transfer step in the test env,
- // so we call it twice and verify both indices are tracked.
- // We use try_create_token and only care that the creator list is updated
- // when the call succeeds. Since fee transfer fails in unit tests we
- // verify the storage key logic by checking the empty-vec baseline and
- // that a second creator gets an independent empty list.
- let creator2 = Address::generate(&env);
-
- let indices1 = client.get_tokens_by_creator(&creator);
- let indices2 = client.get_tokens_by_creator(&creator2);
-
- // Both unknown creators return empty vecs
- assert_eq!(indices1.len(), 0);
- assert_eq!(indices2.len(), 0);
-
- // Confirm they are independent (not the same object)
- assert_eq!(indices1, indices2);
-}
-
-#[test]
-fn test_get_tokens_by_creator_different_creators_are_independent() {
- let (env, client, _admin, _treasury) = setup_env();
- let creator_a = Address::generate(&env);
- let creator_b = Address::generate(&env);
-
- // Neither has tokens — both return empty
- assert_eq!(client.get_tokens_by_creator(&creator_a).len(), 0);
- assert_eq!(client.get_tokens_by_creator(&creator_b).len(), 0);
-}
\ No newline at end of file
+#![cfg(test)]
+
+use super::*;
+use soroban_sdk::{
+ testutils::{Address as _, AuthorizedFunction, AuthorizedInvocation},
+ Address, Env, String,
+};
+
+// ── helpers ──────────────────────────────────────────────────────────────────
+
+fn setup_env() -> (Env, TokenFactoryClient<'static>, Address, Address) {
+ let env = Env::default();
+ env.mock_all_auths();
+
+ let contract_id = env.register_contract(None, TokenFactory);
+ let client = TokenFactoryClient::new(&env, &contract_id);
+
+ let admin = Address::generate(&env);
+ let treasury = Address::generate(&env);
+
+ client.initialize(&admin, &treasury, &1000, &500);
+
+ (env, client, admin, treasury)
+}
+
+// ── pause / unpause ───────────────────────────────────────────────────────────
+
+#[test]
+fn test_initial_state_is_not_paused() {
+ let (_env, client, _admin, _treasury) = setup_env();
+ let state = client.get_state();
+ assert!(!state.paused);
+}
+
+#[test]
+fn test_admin_can_pause() {
+ let (_env, client, admin, _treasury) = setup_env();
+ client.pause(&admin);
+ let state = client.get_state();
+ assert!(state.paused);
+}
+
+#[test]
+fn test_admin_can_unpause() {
+ let (_env, client, admin, _treasury) = setup_env();
+ client.pause(&admin);
+ client.unpause(&admin);
+ let state = client.get_state();
+ assert!(!state.paused);
+}
+
+#[test]
+fn test_non_admin_cannot_pause() {
+ let (env, client, _admin, _treasury) = setup_env();
+ let stranger = Address::generate(&env);
+
+ let result = client.try_pause(&stranger);
+ assert_eq!(result, Err(Ok(Error::Unauthorized)));
+}
+
+#[test]
+fn test_non_admin_cannot_unpause() {
+ let (env, client, admin, _treasury) = setup_env();
+ let stranger = Address::generate(&env);
+
+ client.pause(&admin);
+ let result = client.try_unpause(&stranger);
+ assert_eq!(result, Err(Ok(Error::Unauthorized)));
+}
+
+// ── paused blocks create_token, mint_tokens, set_metadata ────────────────────
+
+#[test]
+fn test_create_token_blocked_when_paused() {
+ let (env, client, admin, _treasury) = setup_env();
+ client.pause(&admin);
+
+ let creator = Address::generate(&env);
+ let result = client.try_create_token(
+ &creator,
+ &String::from_str(&env, "MyToken"),
+ &String::from_str(&env, "MTK"),
+ &7,
+ &1_000_000,
+ &1000,
+ );
+
+ assert_eq!(result, Err(Ok(Error::ContractPaused)));
+}
+
+#[test]
+fn test_mint_tokens_blocked_when_paused() {
+ let (env, client, admin, _treasury) = setup_env();
+ client.pause(&admin);
+
+ let token_address = Address::generate(&env);
+ let recipient = Address::generate(&env);
+
+ let result = client.try_mint_tokens(
+ &token_address,
+ &admin,
+ &recipient,
+ &500,
+ &1000,
+ );
+
+ assert_eq!(result, Err(Ok(Error::ContractPaused)));
+}
+
+#[test]
+fn test_set_metadata_blocked_when_paused() {
+ let (env, client, admin, _treasury) = setup_env();
+ client.pause(&admin);
+
+ let token_address = Address::generate(&env);
+
+ let result = client.try_set_metadata(
+ &token_address,
+ &admin,
+ &String::from_str(&env, "https://example.com/meta.json"),
+ &500,
+ );
+
+ assert_eq!(result, Err(Ok(Error::ContractPaused)));
+}
+
+// ── unpause restores functionality ───────────────────────────────────────────
+
+#[test]
+fn test_create_token_works_after_unpause() {
+ // This test just verifies unpause lifts the block.
+ // create_token will still fail due to fee transfer in test env,
+ // but the error should NOT be ContractPaused.
+ let (env, client, admin, _treasury) = setup_env();
+
+ client.pause(&admin);
+ client.unpause(&admin);
+
+ let creator = Address::generate(&env);
+ let result = client.try_create_token(
+ &creator,
+ &String::from_str(&env, "MyToken"),
+ &String::from_str(&env, "MTK"),
+ &7,
+ &1_000_000,
+ &1000,
+ );
+
+ // Should NOT be ContractPaused — any other error is fine here
+ assert_ne!(result, Err(Ok(Error::ContractPaused)));
+}
+
+// ── burn is NOT blocked by pause ─────────────────────────────────────────────
+
+#[test]
+fn test_burn_not_blocked_when_paused() {
+ let (env, client, admin, _treasury) = setup_env();
+ client.pause(&admin);
+
+ let token_address = Address::generate(&env);
+ let burner = Address::generate(&env);
+
+ // burn will fail because the token isn't real in this unit test,
+ // but the error must NOT be ContractPaused
+ let result = client.try_burn(&token_address, &burner, &100);
+ assert_ne!(result, Err(Ok(Error::ContractPaused)));
+}
+
+// ── transfer_admin ────────────────────────────────────────────────────────────
+
+#[test]
+fn test_admin_can_transfer_ownership() {
+ let (env, client, admin, _treasury) = setup_env();
+ let new_admin = Address::generate(&env);
+
+ client.transfer_admin(&admin, &new_admin);
+
+ let state = client.get_state();
+ assert_eq!(state.admin, new_admin);
+}
+
+#[test]
+fn test_old_admin_loses_privileges_after_transfer() {
+ let (env, client, admin, _treasury) = setup_env();
+ let new_admin = Address::generate(&env);
+
+ client.transfer_admin(&admin, &new_admin);
+
+ // old admin can no longer pause
+ let result = client.try_pause(&admin);
+ assert_eq!(result, Err(Ok(Error::Unauthorized)));
+}
+
+#[test]
+fn test_non_admin_cannot_transfer_admin() {
+ let (env, client, _admin, _treasury) = setup_env();
+ let stranger = Address::generate(&env);
+ let new_admin = Address::generate(&env);
+
+ let result = client.try_transfer_admin(&stranger, &new_admin);
+ assert_eq!(result, Err(Ok(Error::Unauthorized)));
+}
+
+#[test]
+fn test_transfer_admin_to_same_address_fails() {
+ let (_env, client, admin, _treasury) = setup_env();
+
+ let result = client.try_transfer_admin(&admin, &admin);
+ assert_eq!(result, Err(Ok(Error::InvalidParameters)));
+}
+
+// ── get_tokens_by_creator ─────────────────────────────────────────────────────
+
+#[test]
+fn test_get_tokens_by_creator_returns_empty_for_unknown_address() {
+ let (env, client, _admin, _treasury) = setup_env();
+ let stranger = Address::generate(&env);
+
+ let indices = client.get_tokens_by_creator(&stranger);
+ assert_eq!(indices.len(), 0);
+}
+
+#[test]
+fn test_get_tokens_by_creator_returns_correct_indices() {
+ let (env, client, _admin, _treasury) = setup_env();
+ let creator = Address::generate(&env);
+
+ // create_token will fail at the fee-transfer step in the test env,
+ // so we call it twice and verify both indices are tracked.
+ // We use try_create_token and only care that the creator list is updated
+ // when the call succeeds. Since fee transfer fails in unit tests we
+ // verify the storage key logic by checking the empty-vec baseline and
+ // that a second creator gets an independent empty list.
+ let creator2 = Address::generate(&env);
+
+ let indices1 = client.get_tokens_by_creator(&creator);
+ let indices2 = client.get_tokens_by_creator(&creator2);
+
+ // Both unknown creators return empty vecs
+ assert_eq!(indices1.len(), 0);
+ assert_eq!(indices2.len(), 0);
+
+ // Confirm they are independent (not the same object)
+ assert_eq!(indices1, indices2);
+}
+
+#[test]
+fn test_get_tokens_by_creator_different_creators_are_independent() {
+ let (env, client, _admin, _treasury) = setup_env();
+ let creator_a = Address::generate(&env);
+ let creator_b = Address::generate(&env);
+
+ // Neither has tokens — both return empty
+ assert_eq!(client.get_tokens_by_creator(&creator_a).len(), 0);
+ assert_eq!(client.get_tokens_by_creator(&creator_b).len(), 0);
+}