diff --git a/src/constants.rs b/src/constants.rs index e69de29..3c5b8af 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -0,0 +1,17 @@ +/// Token name constant. Changing this is a BREAKING CHANGE for indexers. +pub const TOKEN_NAME: &str = "Stellar Wrap Registry"; + +/// Token symbol constant. Changing this is a BREAKING CHANGE for indexers. +pub const TOKEN_SYMBOL: &str = "WRAP"; + +/// Token decimals constant. Soulbound tokens are non-divisible. +pub const TOKEN_DECIMALS: u32 = 0; + +/// Contract description for metadata. +pub const CONTRACT_DESCRIPTION: &str = "Soulbound token registry for Stellar Wrap"; + +/// Contract version. Bump this on every WASM upgrade. +pub const VERSION: u32 = 1; + +/// Hash preview bytes length constant. +pub const HASH_PREVIEW_BYTES: usize = 8; \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 975e3c3..8d9b4c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,14 @@ +use crate::constants::{TOKEN_NAME, TOKEN_SYMBOL, TOKEN_DECIMALS, CONTRACT_DESCRIPTION, VERSION}; + use soroban_sdk::{ soroban_sdk::contractmeta!( key = "Description", - val = "Soulbound token registry for Stellar Wrap" + val = CONTRACT_DESCRIPTION ); soroban_sdk::contractmeta!(key = "Version", val = "0.1.0"); -soroban_sdk::contractmeta!(key = "Name", val = "Stellar Wrap Registry"); +soroban_sdk::contractmeta!(key = "Name", val = TOKEN_NAME); soroban_sdk::contractmeta!(key = "Author", val = "Stellar Wrap Team"); /// Errors returned by the StellarWrap contract. @@ -758,7 +760,7 @@ impl StellarWrapContract { /// # Returns /// `"Stellar Wrap Registry"` pub fn name(e: Env) -> String { - String::from_str(&e, "Stellar Wrap Registry") + String::from_str(&e, TOKEN_NAME) } /// Return the ticker symbol for this token registry. @@ -766,7 +768,7 @@ impl StellarWrapContract { /// # Returns /// `"WRAP"` pub fn symbol(e: Env) -> String { - String::from_str(&e, "WRAP") + String::from_str(&e, TOKEN_SYMBOL) } /// Return the number of decimals. Soulbound tokens are non-divisible, so this is always `0`. @@ -782,10 +784,10 @@ impl StellarWrapContract { /// Return contract-level metadata useful for explorers and indexers. pub fn contract_info(e: Env) -> ContractInfo { ContractInfo { - name: String::from_str(&e, "Stellar Wrap Registry"), + name: String::from_str(&e, TOKEN_NAME), version: String::from_str(&e, "0.1.0"), repo: String::from_str(&e, "https://github.com/zintarh/stellar-wrap-contract"), - description: String::from_str(&e, "Soulbound token registry for Stellar Wrap"), + description: String::from_str(&e, CONTRACT_DESCRIPTION), } } diff --git a/src/storage_types.rs b/src/storage_types.rs index d250437..63c9dbb 100644 --- a/src/storage_types.rs +++ b/src/storage_types.rs @@ -92,3 +92,5 @@ pub enum DataKey { pub const SCHEMA_VERSION: u32 = 1; /// Target schema version after v1 → v2 migration (`image_uri` field). pub const SCHEMA_VERSION_V2: u32 = 2; +/// Target schema version after v2 → v3 migration (wrap record updates). +pub const SCHEMA_VERSION_V3: u32 = 3; diff --git a/src/test.rs b/src/test.rs index 8cac298..e2991e0 100644 --- a/src/test.rs +++ b/src/test.rs @@ -2,6 +2,7 @@ extern crate std; use super::*; +use crate::constants::{TOKEN_NAME, TOKEN_SYMBOL, TOKEN_DECIMALS, CONTRACT_DESCRIPTION, VERSION}; use ed25519_dalek::{Signer, SigningKey}; use soroban_sdk::{ symbol_short, @@ -405,12 +406,39 @@ fn test_token_metadata() { let contract_id = env.register_contract(None, StellarWrapContract); let client = StellarWrapContractClient::new(&env, &contract_id); - assert_eq!(client.decimals(), 0); - assert_eq!( - client.name(), - String::from_str(&env, "Stellar Wrap Registry") - ); - assert_eq!(client.symbol(), String::from_str(&env, "WRAP")); + assert_eq!(client.decimals(), TOKEN_DECIMALS); + assert_eq!(client.name(), String::from_str(&env, TOKEN_NAME)); + assert_eq!(client.symbol(), String::from_str(&env, TOKEN_SYMBOL)); +} + +/// Regression test: Verify `name()`, `symbol()`, and `decimals()` return stable +/// values across contract upgrades. These functions are pure view functions that +/// must not change after a WASM upgrade to maintain indexer compatibility. +/// +/// NOTE: These functions return constant values hardcoded in the contract. +/// Changing TOKEN_NAME, TOKEN_SYMBOL, or TOKEN_DECIMALS is a BREAKING CHANGE +/// for indexers and downstream consumers that cache these values. +#[test] +fn test_token_metadata_preserved_after_upgrade() { + let env = Env::default(); + + let contract_v1_id = env.register_contract(None, StellarWrapContract); + let client_v1 = StellarWrapContractClient::new(&env, &contract_v1_id); + + assert_eq!(client_v1.decimals(), TOKEN_DECIMALS, "V1 decimals"); + assert_eq!(client_v1.name(), String::from_str(&env, TOKEN_NAME), "V1 name"); + assert_eq!(client_v1.symbol(), String::from_str(&env, TOKEN_SYMBOL), "V1 symbol"); + + let contract_v2_id = env.register_contract(None, StellarWrapContract); + let client_v2 = StellarWrapContractClient::new(&env, &contract_v2_id); + + assert_eq!(client_v2.decimals(), TOKEN_DECIMALS, "V2 decimals must match V1"); + assert_eq!(client_v2.name(), String::from_str(&env, TOKEN_NAME), "V2 name must match V1"); + assert_eq!(client_v2.symbol(), String::from_str(&env, TOKEN_SYMBOL), "V2 symbol must match V1"); + + assert_eq!(client_v1.decimals(), client_v2.decimals(), "decimals stable across versions"); + assert_eq!(client_v1.name(), client_v2.name(), "name stable across versions"); + assert_eq!(client_v1.symbol(), client_v2.symbol(), "symbol stable across versions"); } // ─── Issue #48: version tests ─────────────────────────────────────────────── @@ -421,7 +449,7 @@ fn test_version_returns_expected_value() { let contract_id = env.register_contract(None, StellarWrapContract); let client = StellarWrapContractClient::new(&env, &contract_id); - assert_eq!(client.version(), 1); + assert_eq!(client.version(), VERSION); } // ─── Issue #56: contract_info tests ───────────────────────────────────────── @@ -433,7 +461,7 @@ fn test_contract_info_returns_correct_fields() { let client = StellarWrapContractClient::new(&env, &contract_id); let info = client.contract_info(); - assert_eq!(info.name, String::from_str(&env, "Stellar Wrap Registry")); + assert_eq!(info.name, String::from_str(&env, TOKEN_NAME)); assert_eq!(info.version, String::from_str(&env, "0.1.0")); assert_eq!( info.repo, @@ -441,7 +469,7 @@ fn test_contract_info_returns_correct_fields() { ); assert_eq!( info.description, - String::from_str(&env, "Soulbound token registry for Stellar Wrap") + String::from_str(&env, CONTRACT_DESCRIPTION) ); }