diff --git a/.bolt/mcp.json b/.bolt/mcp.json new file mode 100644 index 0000000..28c6548 --- /dev/null +++ b/.bolt/mcp.json @@ -0,0 +1,7 @@ +{ + "mcpServers": { + "github": { + "enabled": true + } + } +} diff --git a/contracts/creditline-contract/src/errors.rs b/contracts/creditline-contract/src/errors.rs index a639875..8d80ad7 100644 --- a/contracts/creditline-contract/src/errors.rs +++ b/contracts/creditline-contract/src/errors.rs @@ -31,4 +31,5 @@ pub enum CreditLineError { InstallmentAlreadyPaid = 24, InvalidLoanStatus = 25, NotInitialized = 26, + MigrationRequired = 27, } diff --git a/contracts/creditline-contract/src/lib.rs b/contracts/creditline-contract/src/lib.rs index 436bee9..d490a94 100644 --- a/contracts/creditline-contract/src/lib.rs +++ b/contracts/creditline-contract/src/lib.rs @@ -186,11 +186,28 @@ impl CreditLineContract { storage::set_admin(&env, &new_admin); } - /// Upgrade the contract WASM — admin only + /// Migrate contract storage to the latest schema version. + /// Called automatically during upgrade() to handle any data migrations. + /// This is idempotent and safe to call multiple times. + pub fn migrate(env: Env) { + let stored = storage::get_schema_version(&env); + let current = storage::CURRENT_SCHEMA_VERSION; + if stored >= current { + return; + } + // Future: add per-version data migration steps here + storage::set_schema_version(&env, current); + } + + /// Upgrade the contract WASM — admin only. + /// After replacing the WASM, automatically runs migrate() to ensure + /// contract storage is up to date with the new code version. pub fn upgrade(env: Env, new_wasm_hash: soroban_sdk::BytesN<32>) { let admin = storage::get_admin(&env).unwrap_or_else(|err| panic_with_error!(&env, err)); admin.require_auth(); env.deployer().update_current_contract_wasm(new_wasm_hash); + // Self-invoke migrate to run post-upgrade migration logic + env.invoke_contract::<()>(&env.current_contract_address(), &Symbol::new(&env, "migrate"), ().into_val(&env)); } pub fn get_admin(env: Env) -> Result { storage::get_admin(&env) diff --git a/contracts/creditline-contract/src/storage.rs b/contracts/creditline-contract/src/storage.rs index d83117b..cfba5a9 100644 --- a/contracts/creditline-contract/src/storage.rs +++ b/contracts/creditline-contract/src/storage.rs @@ -3,6 +3,20 @@ use soroban_sdk::{contracttype, symbol_short, Address, Env, Symbol, Vec}; use crate::errors::CreditLineError; use crate::types::Loan; +// Schema version key +pub const SCHEMA_VERSION_KEY: Symbol = symbol_short!("SCHEMA_V"); +pub const CURRENT_SCHEMA_VERSION: u32 = 1; + +/// Get the current schema version (0 = unset) +pub fn get_schema_version(env: &Env) -> u32 { + env.storage().instance().get(&SCHEMA_VERSION_KEY).unwrap_or(0) +} + +/// Set the schema version +pub fn set_schema_version(env: &Env, version: u32) { + env.storage().instance().set(&SCHEMA_VERSION_KEY, &version); +} + // Storage keys pub const ADMIN_KEY: Symbol = symbol_short!("ADMIN"); pub const LOAN_COUNTER: Symbol = symbol_short!("LOANCNT"); diff --git a/contracts/liquidity-pool-contract/src/errors.rs b/contracts/liquidity-pool-contract/src/errors.rs index 219f0d5..1fcc261 100644 --- a/contracts/liquidity-pool-contract/src/errors.rs +++ b/contracts/liquidity-pool-contract/src/errors.rs @@ -15,4 +15,5 @@ pub enum LiquidityPoolError { NotCreditLine = 9, ZeroTotalShares = 10, ReentrancyDetected = 11, + MigrationRequired = 12, } diff --git a/contracts/liquidity-pool-contract/src/lib.rs b/contracts/liquidity-pool-contract/src/lib.rs index 5947cbd..d28483d 100644 --- a/contracts/liquidity-pool-contract/src/lib.rs +++ b/contracts/liquidity-pool-contract/src/lib.rs @@ -1,5 +1,5 @@ #![no_std] -use soroban_sdk::{contract, contractimpl, panic_with_error, token, Address, Env}; +use soroban_sdk::{contract, contractimpl, panic_with_error, token, Address, Env, IntoVal, Symbol}; mod errors; mod events; @@ -72,11 +72,28 @@ impl LiquidityPoolContract { storage::set_admin(&env, &new_admin); } - /// Upgrade the contract WASM — admin only + /// Migrate contract storage to the latest schema version. + /// Called automatically during upgrade() to handle any data migrations. + /// This is idempotent and safe to call multiple times. + pub fn migrate(env: Env) { + let stored = storage::get_schema_version(&env); + let current = storage::CURRENT_SCHEMA_VERSION; + if stored >= current { + return; + } + // Future: add per-version data migration steps here + storage::set_schema_version(&env, current); + } + + /// Upgrade the contract WASM — admin only. + /// After replacing the WASM, automatically runs migrate() to ensure + /// contract storage is up to date with the new code version. pub fn upgrade(env: Env, new_wasm_hash: soroban_sdk::BytesN<32>) { let admin = storage::get_admin(&env).unwrap_or_else(|err| panic_with_error!(&env, err)); admin.require_auth(); env.deployer().update_current_contract_wasm(new_wasm_hash); + // Self-invoke migrate to run post-upgrade migration logic + env.invoke_contract::<()>(&env.current_contract_address(), &Symbol::new(&env, "migrate"), ().into_val(&env)); } pub fn get_admin(env: Env) -> Result { storage::get_admin(&env) diff --git a/contracts/liquidity-pool-contract/src/storage.rs b/contracts/liquidity-pool-contract/src/storage.rs index cc35085..eecd5f8 100644 --- a/contracts/liquidity-pool-contract/src/storage.rs +++ b/contracts/liquidity-pool-contract/src/storage.rs @@ -2,6 +2,20 @@ use soroban_sdk::{symbol_short, Address, Env, Symbol}; use crate::errors::LiquidityPoolError; +// Schema version key +pub const SCHEMA_VERSION_KEY: Symbol = symbol_short!("SCHEMA_V"); +pub const CURRENT_SCHEMA_VERSION: u32 = 1; + +/// Get the current schema version (0 = unset) +pub fn get_schema_version(env: &Env) -> u32 { + env.storage().instance().get(&SCHEMA_VERSION_KEY).unwrap_or(0) +} + +/// Set the schema version +pub fn set_schema_version(env: &Env, version: u32) { + env.storage().instance().set(&SCHEMA_VERSION_KEY, &version); +} + // Instance storage keys pub const ADMIN_KEY: Symbol = symbol_short!("ADMIN"); pub const TOKEN_KEY: Symbol = symbol_short!("TOKEN"); diff --git a/contracts/parameters-contract/src/lib.rs b/contracts/parameters-contract/src/lib.rs index 00ecf6e..189b05e 100644 --- a/contracts/parameters-contract/src/lib.rs +++ b/contracts/parameters-contract/src/lib.rs @@ -10,7 +10,7 @@ mod types; pub use errors::ParametersError; pub use types::{default_parameters, ProtocolParameters}; -use soroban_sdk::{contract, contractimpl, panic_with_error, Address, Env}; +use soroban_sdk::{contract, contractimpl, panic_with_error, Address, Env, IntoVal, Symbol}; #[contract] pub struct ParametersContract; @@ -34,7 +34,22 @@ impl ParametersContract { Self::initialize(env, admin, default_parameters()); } - /// Upgrade the contract WASM — admin only + /// Migrate contract storage to the latest schema version. + /// Called automatically during upgrade() to handle any data migrations. + /// This is idempotent and safe to call multiple times. + pub fn migrate(env: Env) { + let stored = storage::get_schema_version(&env); + let current = storage::CURRENT_SCHEMA_VERSION; + if stored >= current { + return; + } + // Future: add per-version data migration steps here + storage::set_schema_version(&env, current); + } + + /// Upgrade the contract WASM — admin only. + /// After replacing the WASM, automatically runs migrate() to ensure + /// contract storage is up to date with the new code version. pub fn upgrade(env: Env, new_wasm_hash: soroban_sdk::BytesN<32>) { let admin = storage::get_admin(&env).unwrap_or_else(|err| panic_with_error!(&env, err)); admin.require_auth(); diff --git a/contracts/parameters-contract/src/storage.rs b/contracts/parameters-contract/src/storage.rs index c1a408c..151f191 100644 --- a/contracts/parameters-contract/src/storage.rs +++ b/contracts/parameters-contract/src/storage.rs @@ -3,6 +3,20 @@ use soroban_sdk::{symbol_short, Address, Env, Symbol}; use crate::errors::ParametersError; use crate::types::ProtocolParameters; +// Schema version key +pub const SCHEMA_VERSION_KEY: Symbol = symbol_short!("SCHEMA_V"); +pub const CURRENT_SCHEMA_VERSION: u32 = 1; + +/// Get the current schema version (0 = unset) +pub fn get_schema_version(env: &Env) -> u32 { + env.storage().instance().get(&SCHEMA_VERSION_KEY).unwrap_or(0) +} + +/// Set the schema version +pub fn set_schema_version(env: &Env, version: u32) { + env.storage().instance().set(&SCHEMA_VERSION_KEY, &version); +} + pub const ADMIN_KEY: Symbol = symbol_short!("ADMIN"); pub const PARAMS_KEY: Symbol = symbol_short!("PARAMS"); pub const REENTRANCY_LOCK: Symbol = symbol_short!("LOCKED"); diff --git a/contracts/reputation-contract/src/lib.rs b/contracts/reputation-contract/src/lib.rs index 0e6e33a..0d072a1 100644 --- a/contracts/reputation-contract/src/lib.rs +++ b/contracts/reputation-contract/src/lib.rs @@ -1,5 +1,5 @@ #![no_std] -use soroban_sdk::{contract, contractimpl, symbol_short, Address, Env, Symbol}; +use soroban_sdk::{contract, contractimpl, symbol_short, Address, Env, IntoVal, Symbol}; // Module imports mod access; @@ -208,7 +208,22 @@ impl ReputationContract { } } - /// Upgrade the contract WASM — admin only + /// Migrate contract storage to the latest schema version. + /// Called automatically during upgrade() to handle any data migrations. + /// This is idempotent and safe to call multiple times. + pub fn migrate(env: Env) { + let stored = storage::get_schema_version(&env); + let current = storage::CURRENT_SCHEMA_VERSION; + if stored >= current { + return; + } + // Future: add per-version data migration steps here + storage::set_schema_version(&env, current); + } + + /// Upgrade the contract WASM — admin only. + /// After replacing the WASM, automatically runs migrate() to ensure + /// contract storage is up to date with the new code version. pub fn upgrade(env: Env, new_wasm_hash: soroban_sdk::BytesN<32>) { let admin = storage::get_admin(&env) .unwrap_or_else(|err| soroban_sdk::panic_with_error!(&env, err)); diff --git a/contracts/reputation-contract/src/storage.rs b/contracts/reputation-contract/src/storage.rs index 3fa299c..c2e9cb1 100644 --- a/contracts/reputation-contract/src/storage.rs +++ b/contracts/reputation-contract/src/storage.rs @@ -2,6 +2,20 @@ use soroban_sdk::{symbol_short, Address, Env, Map, Symbol}; use crate::errors::ReputationError; +// Schema version key +pub const SCHEMA_VERSION_KEY: Symbol = symbol_short!("SCHEMA_V"); +pub const CURRENT_SCHEMA_VERSION: u32 = 1; + +/// Get the current schema version (0 = unset) +pub fn get_schema_version(env: &Env) -> u32 { + env.storage().instance().get(&SCHEMA_VERSION_KEY).unwrap_or(0) +} + +/// Set the schema version +pub fn set_schema_version(env: &Env, version: u32) { + env.storage().instance().set(&SCHEMA_VERSION_KEY, &version); +} + // Storage keys for the reputation contract pub const ADMIN_KEY: Symbol = symbol_short!("ADMIN"); pub const UPDATERS_MAP: Symbol = symbol_short!("UPDATERS"); diff --git a/contracts/vendor-registry-contract/src/lib.rs b/contracts/vendor-registry-contract/src/lib.rs index e88029a..658187d 100644 --- a/contracts/vendor-registry-contract/src/lib.rs +++ b/contracts/vendor-registry-contract/src/lib.rs @@ -11,7 +11,7 @@ mod types; mod tests; use errors::Error; -use soroban_sdk::{contract, contractimpl, Address, Env, String}; +use soroban_sdk::{contract, contractimpl, Address, Env, IntoVal, String, Symbol, Val}; use types::VendorInfo; // Export Error type for external use @@ -171,4 +171,42 @@ impl VendorRegistryContract { storage::get_vendor_count(&env) } + + /// Set the admin address for this contract. + /// Requires authorization from the current admin. + pub fn set_admin(env: Env, new_admin: Address) -> Result<(), Error> { + let old_admin = storage::get_admin(&env)?; + old_admin.require_auth(); + access::require_admin(&env, &old_admin)?; + + storage::set_admin(&env, &new_admin); + Ok(()) + } + + /// Migrate contract storage to the latest schema version. + /// Called automatically during upgrade() to handle any data migrations. + /// This is idempotent and safe to call multiple times. + pub fn migrate(env: Env) -> Result<(), Error> { + let stored = storage::get_schema_version(&env); + let current = storage::CURRENT_SCHEMA_VERSION; + if stored >= current { + return Ok(()); + } + // Future: add per-version data migration steps here + storage::set_schema_version(&env, current); + Ok(()) + } + + /// Upgrade the contract WASM — admin only. + /// After replacing the WASM, automatically runs migrate() to ensure + /// contract storage is up to date with the new code version. + pub fn upgrade(env: Env, new_wasm_hash: soroban_sdk::BytesN<32>) -> Result<(), Error> { + let admin = storage::get_admin(&env)?; + admin.require_auth(); + access::require_admin(&env, &admin)?; + env.deployer().update_current_contract_wasm(new_wasm_hash); + // Self-invoke migrate to run post-upgrade migration logic + env.invoke_contract::(&env.current_contract_address(), &Symbol::new(&env, "migrate"), ().into_val(&env)); + Ok(()) + } } diff --git a/contracts/vendor-registry-contract/src/storage.rs b/contracts/vendor-registry-contract/src/storage.rs index b2b1ff7..34e0e19 100644 --- a/contracts/vendor-registry-contract/src/storage.rs +++ b/contracts/vendor-registry-contract/src/storage.rs @@ -4,6 +4,19 @@ use crate::{ }; use soroban_sdk::{Address, Env}; +// Schema version +pub const CURRENT_SCHEMA_VERSION: u32 = 1; + +/// Get the current schema version (0 = unset) +pub fn get_schema_version(env: &Env) -> u32 { + env.storage().instance().get(&DataKey::SchemaVersion).unwrap_or(0) +} + +/// Set the schema version +pub fn set_schema_version(env: &Env, version: u32) { + env.storage().instance().set(&DataKey::SchemaVersion, &version); +} + pub const PERSISTENT_TTL_THRESHOLD: u32 = 1_036_800; pub const PERSISTENT_TTL_EXTEND_TO: u32 = 2_073_600; diff --git a/contracts/vouching-contract/src/lib.rs b/contracts/vouching-contract/src/lib.rs index e2feb80..600cfb7 100644 --- a/contracts/vouching-contract/src/lib.rs +++ b/contracts/vouching-contract/src/lib.rs @@ -121,6 +121,41 @@ impl VouchingContract { storage::is_mentor(&env, &mentor) } + /// Set the admin address for this contract. + /// Requires authorization from the current admin. + pub fn set_admin(env: Env, new_admin: Address) { + let old_admin = storage::get_admin(&env).unwrap_or_else(|err| panic_with_error!(&env, err)); + old_admin.require_auth(); + Self::require_admin(&env, &old_admin); + + storage::set_admin(&env, &new_admin); + } + + /// Migrate contract storage to the latest schema version. + /// Called automatically during upgrade() to handle any data migrations. + /// This is idempotent and safe to call multiple times. + pub fn migrate(env: Env) { + let stored = storage::get_schema_version(&env); + let current = storage::CURRENT_SCHEMA_VERSION; + if stored >= current { + return; + } + // Future: add per-version data migration steps here + storage::set_schema_version(&env, current); + } + + /// Upgrade the contract WASM — admin only. + /// After replacing the WASM, automatically runs migrate() to ensure + /// contract storage is up to date with the new code version. + pub fn upgrade(env: Env, new_wasm_hash: soroban_sdk::BytesN<32>) { + let admin = storage::get_admin(&env).unwrap_or_else(|err| panic_with_error!(&env, err)); + admin.require_auth(); + Self::require_admin(&env, &admin); + env.deployer().update_current_contract_wasm(new_wasm_hash); + // Self-invoke migrate to run post-upgrade migration logic + env.invoke_contract::<()>(&env.current_contract_address(), &Symbol::new(&env, "migrate"), ().into_val(&env)); + } + fn add_reputation_boost(env: &Env, learner: &Address, boost_amount: u32) { let reputation_contract = storage::get_reputation_contract(env).unwrap_or_else(|err| panic_with_error!(env, err)); diff --git a/contracts/vouching-contract/src/storage.rs b/contracts/vouching-contract/src/storage.rs index 7462cc4..34ef6a1 100644 --- a/contracts/vouching-contract/src/storage.rs +++ b/contracts/vouching-contract/src/storage.rs @@ -5,6 +5,19 @@ use crate::{ types::{DataKey, VouchRecord}, }; +// Schema version +pub const CURRENT_SCHEMA_VERSION: u32 = 1; + +/// Get the current schema version (0 = unset) +pub fn get_schema_version(env: &Env) -> u32 { + env.storage().instance().get(&DataKey::SchemaVersion).unwrap_or(0) +} + +/// Set the schema version +pub fn set_schema_version(env: &Env, version: u32) { + env.storage().instance().set(&DataKey::SchemaVersion, &version); +} + pub const PERSISTENT_TTL_THRESHOLD: u32 = 1_036_800; pub const PERSISTENT_TTL_EXTEND_TO: u32 = 2_073_600; diff --git a/contracts/vouching-contract/src/types.rs b/contracts/vouching-contract/src/types.rs index 0d70929..aba50aa 100644 --- a/contracts/vouching-contract/src/types.rs +++ b/contracts/vouching-contract/src/types.rs @@ -16,6 +16,7 @@ pub struct VouchRecord { #[derive(Clone)] pub enum DataKey { Admin, + SchemaVersion, ReputationContract, VouchBoost, Locked, diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..af2503e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "project", + "lockfileVersion": 3, + "requires": true, + "packages": {} +}