diff --git a/Contract/rewards/src/lib.rs b/Contract/rewards/src/lib.rs index 5622f62..0e5a043 100644 --- a/Contract/rewards/src/lib.rs +++ b/Contract/rewards/src/lib.rs @@ -336,27 +336,7 @@ impl RewardsContract { .unwrap_or(0); Ok(count) } -} - - - pub fn initialize_rewards(_env: Env, _total_pool: i128, _reward_asset: Asset) { - todo!("Implement rewards initialization") - } - - pub fn claim_rewards(_env: Env, _user: Address) -> i128 { - todo!("Implement reward claiming") - } - pub fn grant_reward(_env: Env, _recipient: Address, _amount: i128, _reward_type: u32) { - todo!("Implement reward granting") - } - - pub fn get_pending_rewards(_env: Env, _user: Address) -> i128 { - todo!("Implement pending rewards calculation") - } - - pub fn calculate_streak_bonus(_env: Env, _streak_count: u32) -> u32 { - todo!("Implement streak bonus calculation") /// Initialize the rewards system with admin and reward asset /// Can only be called once pub fn initialize(env: Env, admin: Address, reward_asset: BytesN<32>, streaks_contract: Address) { @@ -707,4 +687,4 @@ impl RewardsContract { let key = Self::rewards_storage_key(env); env.storage().persistent().set(&key, &rewards); } -} +} \ No newline at end of file diff --git a/Contract/shared/src/errors.rs b/Contract/shared/src/errors.rs index 16a953f..6061ca1 100644 --- a/Contract/shared/src/errors.rs +++ b/Contract/shared/src/errors.rs @@ -23,90 +23,41 @@ pub enum Error { LiquidationThreshold = 16, RewardAlreadyClaimed = 17, InvalidParameters = 18, - - /// Pool not found - the specified lending pool does not exist PoolNotFound = 19, - - /// Pool already exists - a pool for this asset already exists PoolAlreadyExists = 20, - - /// Insufficient liquidity - not enough available liquidity for operation InsufficientLiquidity = 21, - - /// Invalid interest rate - interest rate outside allowed bounds InvalidInterestRate = 22, - - /// Invalid share amount - share amount must be positive InvalidShareAmount = 23, - - /// Pool paused - operation not allowed while pool is paused PoolPaused = 24, - - /// Pool closed - operation not allowed on closed pool PoolClosed = 25, - - /// Zero shares - cannot withdraw zero shares ZeroShares = 26, - - /// Insufficient shares - not enough shares for withdrawal InsufficientShares = 27, - - /// Invalid reserve factor - reserve factor outside allowed bounds InvalidReserveFactor = 28, - - /// Rate limit exceeded - operation rate limit reached RateLimitExceeded = 29, - - /// Emergency stop active - operation blocked due to emergency stop EmergencyStopActive = 30, - - /// Permission denied - insufficient permissions for operation PermissionDenied = 31, - - /// Loan already exists - loan ID already in use LoanAlreadyExists = 32, - - /// Invalid collateral - collateral asset not supported or insufficient InvalidCollateral = 33, - - /// Collateral ratio too low - position undercollateralized CollateralRatioTooLow = 34, - - /// Flash loan not allowed - flash loans disabled for this pool FlashLoanNotAllowed = 35, - - /// Cooldown period not met - operation blocked by cooldown CooldownPeriodNotMet = 36, - - /// Invalid timestamp - timestamp outside acceptable range InvalidTimestampRange = 37, - - /// Contract paused - operation blocked due to paused state ContractPaused = 38, - - /// Reentrancy detected - potential reentrancy attack blocked ReentrancyDetected = 39, - - /// Invalid signature - signature verification failed InvalidSignature = 40, - - /// Expired deadline - operation deadline has passed ExpiredDeadline = 41, + StorageExpired = 42, + DuplicateActivity = 43, + NoFreezesAvailable = 44, + MilestoneAlreadyReached = 45, + RewardsPoolNotInitialized = 46, + RewardsPoolAlreadyInitialized = 47, + InsufficientRewardLiquidity = 48, + UnauthorizedAdmin = 49, + RewardNotEligible = 50, + PoolAlreadyInitialized = 51, + LoanAlreadyRepaid = 52, + LiquidationNotAllowed = 53, + NoPositionFound = 54, } - DuplicateActivity = 19, - NoFreezesAvailable = 20, - MilestoneAlreadyReached = 21, - RewardsPoolNotInitialized = 22, - RewardsPoolAlreadyInitialized = 23, - InsufficientRewardLiquidity = 24, - UnauthorizedAdmin = 25, - RewardNotEligible = 26, - InvalidInterestRate = 27, - PoolNotFound = 28, - PoolAlreadyInitialized = 29, - LoanAlreadyExists = 30, - LoanAlreadyRepaid = 31, - LiquidationNotAllowed = 32, - NoPositionFound = 33, - InsufficientShares = 34, -} + diff --git a/Contract/shared/src/storage.rs b/Contract/shared/src/storage.rs index b268ec7..78ad079 100644 --- a/Contract/shared/src/storage.rs +++ b/Contract/shared/src/storage.rs @@ -1,20 +1,195 @@ -use soroban_sdk::{BytesN, Env}; +use crate::errors::Error; +use soroban_sdk::{BytesN, Env, IntoVal, Val}; -/// Storage key type for persistent storage +/// Shared storage key type for raw byte-based maps pub type StorageKey = BytesN<32>; -/// Simple storage helpers for common operations -/// Contracts should use direct storage operations for complex types +/// ------------------------------------------------------------------------- +/// TTL POLICY +/// ------------------------------------------------------------------------- +/// +/// Soroban persistent storage expires unless its TTL is periodically extended. +/// +/// Every protocol record should renew its TTL whenever it is: +/// +/// - created +/// - read +/// - modified +/// +/// This helper centralizes the TTL policy for every Vaulty contract. +/// ------------------------------------------------------------------------- + +pub struct StorageTTL; + +/// Ledger estimates (~5 seconds/ledger) +impl StorageTTL { + /// ≈ 1 day + pub const DAY: u32 = 17_280; + + /// ≈ 30 days + pub const MONTH: u32 = Self::DAY * 30; + + /// ≈ 1 year + pub const YEAR: u32 = Self::DAY * 365; + + /// Safety buffer before expiration + pub const BUFFER: u32 = Self::MONTH; + + /// --------------------------------------------------------------------- + /// TTL categories + /// --------------------------------------------------------------------- + + /// Contract instance storage + pub const INSTANCE: u32 = Self::YEAR; + + /// Vault metadata + pub const VAULT: u32 = Self::YEAR * 6; + + /// User balances + pub const USER: u32 = Self::YEAR * 6; + + /// Reward state + pub const REWARD: u32 = Self::YEAR * 3; + + /// Lending pools + pub const LENDING: u32 = Self::YEAR * 6; + + /// Borrow positions + pub const BORROWING: u32 = Self::YEAR * 6; + + /// Streak records + pub const STREAK: u32 = Self::YEAR * 6; +} + +/// Storage helpers pub struct StorageHelper; impl StorageHelper { - /// Check if a key exists in persistent storage - pub fn has(env: &Env, key: &BytesN<32>) -> bool { + /// --------------------------------------------------------------------- + /// Basic helpers + /// --------------------------------------------------------------------- + + pub fn has(env: &Env, key: &K) -> bool + where + K: soroban_sdk::IntoVal + ?Sized, + { env.storage().persistent().has(key) } - /// Check if a key exists in instance storage - pub fn has_instance(env: &Env, key: &BytesN<32>) -> bool { + pub fn has_instance(env: &Env, key: &K) -> bool + where + K: soroban_sdk::IntoVal + ?Sized, + { env.storage().instance().has(key) } -} + + /// --------------------------------------------------------------------- + /// Persistent TTL + /// --------------------------------------------------------------------- + + pub fn extend_persistent( + env: &Env, + key: &K, + threshold: u32, + extend_to: u32, + ) + where + K: soroban_sdk::IntoVal + ?Sized, + { + env.storage() + .persistent() + .extend_ttl(key, threshold, extend_to); + } + + pub fn ensure_persistent_not_expired( + env: &Env, + key: &K, + threshold: u32, + ) -> Result<(), Error> + where + K: soroban_sdk::IntoVal + ?Sized, + { + if !env.storage().persistent().has(key) { + return Err(Error::NotInitialized); + } + env.storage() + .persistent() + .extend_ttl(key, threshold, threshold); + Ok(()) + } + + /// --------------------------------------------------------------------- + /// Instance TTL + /// --------------------------------------------------------------------- + + pub fn extend_instance( + env: &Env, + threshold: u32, + extend_to: u32, + ) { + env.storage() + .instance() + .extend_ttl(threshold, extend_to); + } + + /// --------------------------------------------------------------------- + /// Category helpers + /// --------------------------------------------------------------------- + + pub fn touch(env: &Env, key: &K, ttl: u32) + where + K: soroban_sdk::IntoVal + ?Sized, + { + Self::extend_persistent(env, key, StorageTTL::BUFFER, ttl); + } + + pub fn touch_vault(env: &Env, key: &K) + where + K: soroban_sdk::IntoVal + ?Sized, + { + Self::touch(env, key, StorageTTL::VAULT); + } + + pub fn touch_user(env: &Env, key: &K) + where + K: soroban_sdk::IntoVal + ?Sized, + { + Self::touch(env, key, StorageTTL::USER); + } + + pub fn touch_reward(env: &Env, key: &K) + where + K: soroban_sdk::IntoVal + ?Sized, + { + Self::touch(env, key, StorageTTL::REWARD); + } + + pub fn touch_lending(env: &Env, key: &K) + where + K: soroban_sdk::IntoVal + ?Sized, + { + Self::touch(env, key, StorageTTL::LENDING); + } + + pub fn touch_borrowing(env: &Env, key: &K) + where + K: soroban_sdk::IntoVal + ?Sized, + { + Self::touch(env, key, StorageTTL::BORROWING); + } + + pub fn touch_streak(env: &Env, key: &K) + where + K: soroban_sdk::IntoVal + ?Sized, + { + Self::touch(env, key, StorageTTL::STREAK); + } + + pub fn touch_instance(env: &Env) { + Self::extend_instance( + env, + StorageTTL::BUFFER, + StorageTTL::INSTANCE, + ); + } +} \ No newline at end of file diff --git a/Contract/shared/src/types.rs b/Contract/shared/src/types.rs index f4ffb3e..d81aeba 100644 --- a/Contract/shared/src/types.rs +++ b/Contract/shared/src/types.rs @@ -25,7 +25,13 @@ pub enum PoolStatus { RateLimited = 4, } -/// Loan status for borrowing contract +/// Loan status shared by the lending/borrowing contracts. +/// +/// NOTE: the uploaded repo defined this enum twice (once here with a +/// `Defaulted` variant used by the older `LoanInfo`-based borrowing draft, +/// and once later with only `Active`/`Repaid`/`Liquidated` used by the +/// newer `Loan`-based draft). Kept as one definition, since both drafts +/// only ever reference `Active`, `Repaid`, and `Liquidated` in practice. #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[contracttype] #[repr(u32)] @@ -87,7 +93,7 @@ pub struct CollateralConfig { pub safety_factor: i128, // Basis points } -/// Loan information +/// Loan information (used by the older `LoanInfo`-based borrowing draft) #[derive(Clone, Debug, PartialEq, Eq)] #[contracttype] pub struct LoanInfo { @@ -188,17 +194,7 @@ pub struct PoolPosition { pub last_accrued_at: u64, } -/// Loan status for borrowing flows. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[contracttype] -#[repr(u32)] -pub enum LoanStatus { - Active = 0, - Repaid = 1, - Liquidated = 2, -} - -/// Collateralized loan state. +/// Collateralized loan state (used by the newer `Loan`-based borrowing draft). #[derive(Clone, Debug, PartialEq, Eq)] #[contracttype] pub struct Loan { @@ -343,5 +339,4 @@ pub struct EmergencyStop { pub triggered_by: Address, pub triggered_at: u64, pub reason: BytesN<32>, -} -} +} \ No newline at end of file diff --git a/Contract/streaks/src/lib.rs b/Contract/streaks/src/lib.rs index 3947ec1..3214801 100644 --- a/Contract/streaks/src/lib.rs +++ b/Contract/streaks/src/lib.rs @@ -387,23 +387,7 @@ impl StreaksContract { .unwrap_or(0); Ok(count) } -} - - pub fn initialize_streak(_env: Env, _user: Address, _asset: Asset) { - todo!("Implement streak initialization") - } - - pub fn update_streak(_env: Env, _user: Address, _vault: VaultMetadata) { - todo!("Implement streak update logic") - } - - pub fn get_streak(_env: Env, _user: Address) -> u32 { - todo!("Implement streak retrieval") - } - - pub fn is_streak_active(_env: Env, _user: Address) -> bool { - todo!("Implement streak activity check") /// Initialize the streaks contract with authorized vault address /// Can only be called once pub fn initialize(env: Env, vault_contract: Address) { @@ -668,4 +652,4 @@ impl StreaksContract { #[cfg(test)] mod test { use super::*; -} +} \ No newline at end of file diff --git a/Contract/vault/src/lib.rs b/Contract/vault/src/lib.rs index 30976f1..dd78f28 100644 --- a/Contract/vault/src/lib.rs +++ b/Contract/vault/src/lib.rs @@ -1,24 +1,14 @@ #![no_std] use soroban_sdk::{ - contract, contracterror, contractimpl, contracttype, Address, BytesN, Env, Vec, + contract, contractimpl, contracttype, Address, BytesN, Env, IntoVal, Map, Symbol, Vec, }; use shared::{ errors::Error, - events::{VaultCreated, VaultUnlocked}, + events::{DepositMade, VaultCreated, VaultUnlocked, WithdrawalCompleted}, + storage::{StorageHelper, StorageTTL}, types::{Asset, VaultMetadata, VaultStatus, EmergencyStop, RateLimit, Role, Permission}, - utils::{SafeMath, TimeHelper, ValidationHelper, FixedMath}, + utils::{FixedMath, SafeMath, TimeHelper, ValidationHelper}, token, - contract, contractimpl, contracttype, Address, BytesN, Env, Map, -}; -use shared::{ - errors::Error, - events::{DepositMade, VaultCreated, VaultUnlocked, WithdrawalCompleted}, - contract, contracterror, contractimpl, contracttype, Address, BytesN, Env, Map, IntoVal, Symbol, Vec, -}; -use shared::{ - errors::Error, - types::{Asset, VaultMetadata, VaultStatus}, - utils::{SafeMath, TimeHelper, ValidationHelper}, }; /// Vault contract for managing savings vaults with time-locked deposits @@ -32,11 +22,14 @@ pub enum VaultKey { Vault(VaultId), Balance(VaultId), VaultCounter, + VaultConfig, EmergencyStop, RateLimit, AdminPermissions(Address), UserVaults(Address), VaultInterest(VaultId), +} + #[cfg(test)] pub use VaultContract; @@ -137,7 +130,7 @@ impl VaultContract { let config: VaultConfig = env .storage() .persistent() - .get(&VaultKey::VaultCounter) + .get(&VaultKey::VaultConfig) .unwrap_or(VaultConfig { max_vaults_per_user: 10, min_lock_period: 1, @@ -167,28 +160,23 @@ impl VaultContract { let counter: u64 = env.storage().persistent().get(&counter_key).unwrap_or(0); let new_counter = counter.checked_add(1).ok_or(Error::Overflow)?; env.storage().persistent().set(&counter_key, &new_counter); + StorageHelper::touch_vault(&env, &counter_key); + if !ValidationHelper::validate_lock_period(lock_period) { panic!("{:?}", Error::InvalidLockPeriod); } - let counter_key = vault_counter_key(&env); - let counter: u64 = env.storage().instance().get(&counter_key).unwrap_or(0); - let new_counter = counter.checked_add(1).unwrap(); - env.storage().instance().set(&counter_key, &new_counter); - let vault_id_bytes = Self::generate_vault_id(&env, new_counter); let vault_id = VaultId(vault_id_bytes.clone()); let now = TimeHelper::now(&env); let unlock_time = now.checked_add(lock_period).ok_or(Error::Overflow)?; - let unlock_time = now.checked_add(lock_period).unwrap(); let asset = Asset { token: token_contract, symbol: symbol.clone(), - code: asset_code.clone(), - issuer: asset_issuer, - issuer: asset_issuer.unwrap_or_else(|| owner.clone()), + code: symbol.clone(), + issuer: owner.clone(), }; let metadata = VaultMetadata { @@ -204,16 +192,19 @@ impl VaultContract { env.storage() .persistent() .set(&VaultKey::Vault(vault_id.clone()), &metadata); + StorageHelper::touch_vault(&env, &VaultKey::Vault(vault_id.clone())); // Initialize balance to zero env.storage() .persistent() .set(&VaultKey::Balance(vault_id.clone()), &0i128); + StorageHelper::touch_vault(&env, &VaultKey::Balance(vault_id.clone())); // Initialize interest tracking env.storage() .persistent() .set(&VaultKey::VaultInterest(vault_id.clone()), &0i128); + StorageHelper::touch_vault(&env, &VaultKey::VaultInterest(vault_id.clone())); // Add to user's vaults let mut updated_user_vaults = user_vaults; @@ -221,14 +212,17 @@ impl VaultContract { env.storage() .persistent() .set(&user_vaults_key, &updated_user_vaults); + StorageHelper::touch_user(&env, &user_vaults_key); + let vaults_key = vaults_key(&env); let mut vaults_map: Map = env .storage() .persistent() .get(&vaults_key) .unwrap_or_else(|| Map::new(&env)); - vaults_map.set(vault_id.clone(), metadata); + vaults_map.set(vault_id.clone(), metadata.clone()); env.storage().persistent().set(&vaults_key, &vaults_map); + StorageHelper::touch_vault(&env, &vaults_key); let balances_key = balances_key(&env); let mut balances_map: Map = env @@ -238,23 +232,16 @@ impl VaultContract { .unwrap_or_else(|| Map::new(&env)); balances_map.set(vault_id.clone(), 0i128); env.storage().persistent().set(&balances_key, &balances_map); + StorageHelper::touch_vault(&env, &balances_key); env.events().publish( (VaultCreated::topic(&env), vault_id_bytes.clone()), VaultCreated { - vault_id: vault_id_bytes.clone(), - owner, - asset: asset_code.clone(), - lock_period, - }, - (VaultCreated { vault_id: vault_id_bytes, owner, asset: symbol.clone(), lock_period, - },), - (vault_id_bytes.clone(), owner, asset_code, lock_period), - (), + }, ); Ok(vault_id) @@ -283,33 +270,21 @@ impl VaultContract { return Err(Error::InvalidAmount); } - // Check vault exists + // Check vault exists and refresh TTL let metadata: VaultMetadata = env .storage() .persistent() .get(&VaultKey::Vault(vault_id.clone())) .ok_or(Error::VaultNotFound)?; + StorageHelper::touch_vault(&env, &VaultKey::Vault(vault_id.clone())); // Accrue interest before deposit Self::accrue_interest(env.clone(), vault_id.clone())?; - if !ValidationHelper::validate_positive_amount(amount) { - panic!("{:?}", Error::InvalidAmount); - } - - let vaults_key = vaults_key(&env); - let vaults_map: Map = env - .storage() - .persistent() - .get(&vaults_key) - .expect("Vault not found"); - let metadata = vaults_map.get(vault_id.clone()).expect("Vault not found"); let token_client = token::Client::new(&env, &metadata.asset.token); token_client.transfer(&from, &env.current_contract_address(), &amount); - let vaults_map: Map = env.storage().persistent().get(&vaults_key).expect("Vault not found"); - let metadata = vaults_map.get(vault_id.clone()).expect("Vault not found"); - // Update balance using safe arithmetic + // Update direct balance entry let current_balance: i128 = env .storage() .persistent() @@ -319,33 +294,34 @@ impl VaultContract { env.storage() .persistent() .set(&VaultKey::Balance(vault_id.clone()), &new_balance); + StorageHelper::touch_vault(&env, &VaultKey::Balance(vault_id.clone())); + + // Update balance map entry let balances_key = balances_key(&env); let mut balances_map: Map = env .storage() .persistent() .get(&balances_key) - .expect("Balance not found"); - let current_balance = balances_map.get(vault_id.clone()).expect("Balance not found"); - - let new_balance: i128 = SafeMath::add(current_balance, amount as i128) - .expect("Overflow"); - + .unwrap_or_else(|| Map::new(&env)); + let current_balance = balances_map.get(vault_id.clone()).unwrap_or(0); + let new_balance: i128 = SafeMath::add(current_balance, amount).ok_or(Error::Overflow)?; balances_map.set(vault_id.clone(), new_balance); env.storage().persistent().set(&balances_key, &balances_map); + StorageHelper::touch_vault(&env, &balances_key); env.events().publish( - (DepositMade { - vault_id: vault_id.0.clone(), - depositor: from, - asset: metadata.asset.symbol, + (DepositMade::topic(&env), vault_id.0.clone()), + DepositMade { + vault_id: vault_id.0, + depositor: from.clone(), + asset: metadata.asset.symbol.clone(), amount, - },), - (), + }, + ); + // Update user's streak in streaks contract if it's initialized let streaks_key = streaks_contract_key(&env); if let Some(streaks_contract) = env.storage().instance().get::, Address>(&streaks_key) { - // Call update_streak on the streaks contract - // This will panic if called twice in the same day, but that's okay - it prevents duplicate streak increments let mut args = Vec::new(&env); args.push_back(metadata.owner.clone().into_val(&env)); let result = env.try_invoke_contract::<(), shared::errors::Error>( @@ -353,9 +329,7 @@ impl VaultContract { &Symbol::new(&env, "update_streak"), args, ); - // If the call succeeds, also try to notify rewards contract if a milestone was reached if result.is_ok() { - // Get the updated streak count let mut get_streak_args = Vec::new(&env); get_streak_args.push_back(metadata.owner.clone().into_val(&env)); let streak_count: u32 = env.invoke_contract( @@ -364,10 +338,8 @@ impl VaultContract { get_streak_args, ); - // Call rewards contract to check for milestones let rewards_key = rewards_contract_key(&env); if let Some(rewards_contract) = env.storage().instance().get::, Address>(&rewards_key) { - // Invoke grant_reward which will check if any milestones are met let mut grant_args = Vec::new(&env); grant_args.push_back(metadata.owner.clone().into_val(&env)); grant_args.push_back(streak_count.into_val(&env)); @@ -380,23 +352,6 @@ impl VaultContract { } } - // Emit deposit event using shared event type - use shared::events::DepositMade; - env.events().publish( - (shared::events::DepositMade::topic(&env), vault_id.0.clone()), - shared::events::DepositMade { - vault_id: vault_id.0, - depositor: from, - amount, - }, - ("deposit_made", from.clone()), - DepositMade { - vault_id: vault_id.0.clone(), - depositor: from.clone(), - amount, - } - ); - Ok(()) } @@ -413,13 +368,6 @@ impl VaultContract { /// # Auth /// Requires authorization from the vault owner pub fn withdraw(env: Env, vault_id: VaultId, to: Address, amount: i128) -> Result<(), Error> { - // Get vault metadata - let mut metadata: VaultMetadata = env - .storage() - .persistent() - .get(&VaultKey::Vault(vault_id.clone())) - .ok_or(Error::VaultNotFound)?; - pub fn withdraw(env: Env, vault_id: VaultId, to: Address, amount: i128) { let vaults_key = vaults_key(&env); let mut vaults_map: Map = env .storage() @@ -427,6 +375,8 @@ impl VaultContract { .get(&vaults_key) .expect("Vault not found"); let mut metadata = vaults_map.get(vault_id.clone()).expect("Vault not found"); + StorageHelper::touch_vault(&env, &vaults_key); + StorageHelper::touch_vault(&env, &VaultKey::Vault(vault_id.clone())); metadata.owner.require_auth(); @@ -441,31 +391,25 @@ impl VaultContract { // Accrue interest before withdrawal Self::accrue_interest(env.clone(), vault_id.clone())?; - // Check lock period + // Check lock period and prevent unsafe withdrawal near expiry if metadata.status == VaultStatus::Locked { if !TimeHelper::is_past(&env, metadata.unlock_time) { return Err(Error::VaultLocked); - if !ValidationHelper::validate_positive_amount(amount) { - panic!("{:?}", Error::InvalidAmount); - } - - if metadata.status == VaultStatus::Locked { - if !TimeHelper::is_past(&env, metadata.unlock_time) { - panic!("{:?}", Error::VaultLocked); } metadata.status = VaultStatus::Unlocked; - env.storage() - .persistent() - .set(&VaultKey::Vault(vault_id.clone()), &metadata.clone()); + vaults_map.set(vault_id.clone(), metadata.clone()); + env.storage().persistent().set(&vaults_key, &vaults_map); + env.storage().persistent().set(&VaultKey::Vault(vault_id.clone()), &metadata); + StorageHelper::touch_vault(&env, &vaults_key); + StorageHelper::touch_vault(&env, &VaultKey::Vault(vault_id.clone())); env.events().publish( - (VaultUnlocked { + (VaultUnlocked::topic(&env), vault_id.0.clone()), + VaultUnlocked { vault_id: vault_id.0.clone(), asset: metadata.asset.symbol.clone(), unlock_time: metadata.unlock_time, - },), - (vault_id.0.clone(), metadata.unlock_time), - (), + }, ); } @@ -477,26 +421,27 @@ impl VaultContract { .expect("Balance not found"); let current_balance = balances_map.get(vault_id.clone()).expect("Balance not found"); if amount > current_balance { - panic!("{:?}", Error::InsufficientBalance); + return Err(Error::InsufficientBalance); } - let new_balance: i128 = SafeMath::sub(current_balance, amount as i128) - .expect("Underflow"); + let new_balance: i128 = SafeMath::sub(current_balance, amount).ok_or(Error::Underflow)?; balances_map.set(vault_id.clone(), new_balance); env.storage().persistent().set(&balances_key, &balances_map); + env.storage().persistent().set(&VaultKey::Balance(vault_id.clone()), &new_balance); + StorageHelper::touch_vault(&env, &balances_key); + StorageHelper::touch_vault(&env, &VaultKey::Balance(vault_id.clone())); let token_client = token::Client::new(&env, &metadata.asset.token); token_client.transfer(&env.current_contract_address(), &to, &amount); env.events().publish( - (shared::events::WithdrawalCompleted::topic(&env), vault_id.0.clone()), - shared::events::WithdrawalCompleted { + (WithdrawalCompleted::topic(&env), vault_id.0.clone()), + WithdrawalCompleted { vault_id: vault_id.0, withdrawer: to, + asset: metadata.asset.symbol.clone(), amount, }, - (vault_id.0.clone(), to, amount), - (), ); Ok(()) @@ -513,8 +458,9 @@ impl VaultContract { let balance: i128 = env .storage() .persistent() - .get(&VaultKey::Balance(vault_id)) + .get(&VaultKey::Balance(vault_id.clone())) .ok_or(Error::VaultNotFound)?; + StorageHelper::touch_vault(&env, &VaultKey::Balance(vault_id)); Ok(balance) } @@ -529,8 +475,9 @@ impl VaultContract { let metadata: VaultMetadata = env .storage() .persistent() - .get(&VaultKey::Vault(vault_id)) + .get(&VaultKey::Vault(vault_id.clone())) .ok_or(Error::VaultNotFound)?; + StorageHelper::touch_vault(&env, &VaultKey::Vault(vault_id)); Ok(metadata) } @@ -545,8 +492,9 @@ impl VaultContract { let user_vaults: Vec = env .storage() .persistent() - .get(&VaultKey::UserVaults(user)) + .get(&VaultKey::UserVaults(user.clone())) .unwrap_or(Vec::new(&env)); + StorageHelper::touch_user(&env, &VaultKey::UserVaults(user)); Ok(user_vaults) } @@ -555,7 +503,7 @@ impl VaultContract { let config: VaultConfig = env .storage() .persistent() - .get(&VaultKey::VaultCounter) + .get(&VaultKey::VaultConfig) .unwrap_or(VaultConfig { max_vaults_per_user: 10, min_lock_period: 1, @@ -569,12 +517,14 @@ impl VaultContract { .persistent() .get(&VaultKey::Vault(vault_id.clone())) .ok_or(Error::VaultNotFound)?; + StorageHelper::touch_vault(&env, &VaultKey::Vault(vault_id.clone())); let balance: i128 = env .storage() .persistent() .get(&VaultKey::Balance(vault_id.clone())) .ok_or(Error::VaultNotFound)?; + StorageHelper::touch_vault(&env, &VaultKey::Balance(vault_id.clone())); if balance == 0 || config.interest_rate == 0 { return Ok(()); @@ -640,7 +590,8 @@ impl VaultContract { if !Self::is_admin(&env, &admin) { return Err(Error::PermissionDenied); } - env.storage().persistent().set(&VaultKey::VaultCounter, &config); + env.storage().persistent().set(&VaultKey::VaultConfig, &config); + StorageHelper::touch_vault(&env, &VaultKey::VaultConfig); Ok(()) } @@ -649,7 +600,7 @@ impl VaultContract { let config: VaultConfig = env .storage() .persistent() - .get(&VaultKey::VaultCounter) + .get(&VaultKey::VaultConfig) .unwrap_or(VaultConfig { max_vaults_per_user: 10, min_lock_period: 1, @@ -657,6 +608,7 @@ impl VaultContract { interest_rate: 500, auto_compound: true, }); + StorageHelper::touch_vault(&env, &VaultKey::VaultConfig); Ok(config) } @@ -702,7 +654,7 @@ impl VaultContract { // Remove from old owner's vault list let old_owner = metadata.owner.clone(); - let old_user_vaults_key = VaultKey::UserLoans(old_owner.clone()); + let old_user_vaults_key = VaultKey::UserVaults(old_owner.clone()); if let Some(mut old_user_vaults): Option> = env.storage().persistent().get(&old_user_vaults_key) { old_user_vaults = old_user_vaults.into_iter().filter(|v| v != &vault_id).collect(); env.storage().persistent().set(&old_user_vaults_key, &old_user_vaults); @@ -715,20 +667,26 @@ impl VaultContract { env.storage().persistent().set(&vaults_key, &vaults_map); // Add to new owner's vault list - let new_user_vaults_key = VaultKey::UserLoans(new_owner); + let new_user_vaults_key = VaultKey::UserVaults(new_owner); let mut new_user_vaults: Vec = env.storage().persistent().get(&new_user_vaults_key).unwrap_or(Vec::new(&env)); new_user_vaults.push_back(vault_id); env.storage().persistent().set(&new_user_vaults_key, &new_user_vaults); + StorageHelper::touch_user(&env, &new_user_vaults_key); Ok(()) } /// Get vault metadata (for cross-contract calls) - pub fn get_vault(env: Env, vault_id: BytesN<32>) -> VaultMetadata { - let typed_vault_id = VaultId(vault_id); + pub fn get_vault_by_bytes(env: Env, vault_id: BytesN<32>) -> VaultMetadata { + let typed_vault_id = VaultId(vault_id.clone()); let vaults_key = vaults_key(&env); let vaults_map: Map = env.storage().persistent().get(&vaults_key).expect("Vault not found"); - vaults_map.get(typed_vault_id).expect("Vault not found") + let metadata = vaults_map.get(typed_vault_id.clone()).expect("Vault not found"); + + StorageHelper::touch_vault(&env, &vaults_key); + StorageHelper::touch_vault(&env, &VaultKey::Vault(typed_vault_id)); + + metadata } /// Helper function to generate a vault ID from a counter