diff --git a/backstop/src/backstop/deposit.rs b/backstop/src/backstop/deposit.rs index 583c6b74..4448d567 100644 --- a/backstop/src/backstop/deposit.rs +++ b/backstop/src/backstop/deposit.rs @@ -121,7 +121,6 @@ mod tests { e.as_contract(&backstop_address, || { execute_deposit(&e, &samwise, &pool_0_id, 100_0000001); - // TODO: Handle token errors gracefully assert!(false); }); } diff --git a/backstop/src/backstop/user.rs b/backstop/src/backstop/user.rs index f1739900..39ad4016 100644 --- a/backstop/src/backstop/user.rs +++ b/backstop/src/backstop/user.rs @@ -1,6 +1,9 @@ use soroban_sdk::{contracttype, panic_with_error, vec, Env, Vec}; -use crate::errors::BackstopError; +use crate::{ + constants::{MAX_Q4W_SIZE, Q4W_LOCK_TIME}, + errors::BackstopError, +}; /// A deposit that is queued for withdrawal #[derive(Clone)] @@ -51,14 +54,15 @@ impl UserBalance { if self.shares < to_q { panic_with_error!(e, BackstopError::BalanceError); } + if self.q4w.len() >= MAX_Q4W_SIZE { + panic_with_error!(e, BackstopError::TooManyQ4WEntries); + } self.shares = self.shares - to_q; // user has enough tokens to withdrawal, add Q4W - // TODO: Consider capping how many active Q4Ws a user can have - let twentyone_days_in_sec = 21 * 24 * 60 * 60; let new_q4w = Q4W { amount: to_q, - exp: e.ledger().timestamp() + twentyone_days_in_sec, + exp: e.ledger().timestamp() + Q4W_LOCK_TIME, }; self.q4w.push_back(new_q4w.clone()); } @@ -210,6 +214,75 @@ mod tests { assert_eq_vec_q4w(&user.q4w, &cur_q4w); } + #[test] + fn test_q4w_new_to_max_works() { + let e = Env::default(); + let exp = 12592000; + let mut cur_q4w = vec![&e]; + for i in 0..20 { + cur_q4w.push_back(Q4W { + amount: 200, + exp: exp + i, + }); + } + let mut user = UserBalance { + shares: 1000, + q4w: cur_q4w.clone(), + }; + + e.ledger().set(LedgerInfo { + protocol_version: 20, + sequence_number: 1, + timestamp: 11000000, + network_id: Default::default(), + base_reserve: 10, + min_temp_entry_ttl: 10, + min_persistent_entry_ttl: 10, + max_entry_ttl: 3110400, + }); + + let to_queue = 500; + user.queue_shares_for_withdrawal(&e, to_queue); + cur_q4w.push_back(Q4W { + amount: to_queue, + exp: 11000000 + 21 * 24 * 60 * 60, + }); + assert_eq_vec_q4w(&user.q4w, &cur_q4w); + } + + #[test] + #[should_panic(expected = "Error(Contract, #1007)")] + fn test_q4w_new_over_max_panics() { + let e = Env::default(); + + let exp = 12592000; + let mut cur_q4w = vec![&e]; + for i in 0..21 { + cur_q4w.push_back(Q4W { + amount: 200, + exp: exp + i, + }); + } + let mut user = UserBalance { + shares: 1000, + q4w: cur_q4w.clone(), + }; + + e.ledger().set(LedgerInfo { + protocol_version: 20, + sequence_number: 1, + timestamp: 11000000, + network_id: Default::default(), + base_reserve: 10, + min_temp_entry_ttl: 10, + min_persistent_entry_ttl: 10, + max_entry_ttl: 3110400, + }); + + let to_queue = 500; + user.queue_shares_for_withdrawal(&e, to_queue); + } + #[test] #[should_panic(expected = "Error(Contract, #10)")] fn test_q4w_over_shares_panics() { diff --git a/backstop/src/constants.rs b/backstop/src/constants.rs index 9c28aa37..d2bd2bad 100644 --- a/backstop/src/constants.rs +++ b/backstop/src/constants.rs @@ -5,3 +5,10 @@ pub const SCALAR_7: i128 = 1_0000000; /// actual deployment time and should not be considered accruate. It is only used to determine reward /// zone size on ~90 day intervals, starting at 10 on or before April 15th, 2024 00:00:00 UTC. pub const BACKSTOP_EPOCH: u64 = 1713139200; + +/// The maximum amount of active Q4W entries that a user can have against a single backstop. +/// Set such that a user can create a maximum of 1 entry per day over the 21 day lock period. +pub const MAX_Q4W_SIZE: u32 = 21; + +/// The time in seconds that a Q4W entry is locked for (21 days). +pub const Q4W_LOCK_TIME: u64 = 21 * 24 * 60 * 60; diff --git a/backstop/src/emissions/manager.rs b/backstop/src/emissions/manager.rs index a3206aec..5c2f3b36 100644 --- a/backstop/src/emissions/manager.rs +++ b/backstop/src/emissions/manager.rs @@ -93,8 +93,6 @@ pub fn gulp_emissions(e: &Env) -> i128 { let mut rz_balance: Vec = vec![e]; - // TODO: Potential to assume optimization of backstop token balances ~= RZ tokens - // However, linear iteration over the RZ will still occur // fetch total tokens of BLND in the reward zone let mut total_non_queued_tokens: i128 = 0; for rz_pool_index in 0..rz_len { @@ -140,12 +138,12 @@ pub fn gulp_pool_emissions(e: &Env, pool_id: &Address) -> i128 { let blnd_token_client = TokenClient::new(e, &storage::get_blnd_token(e)); let current_allowance = blnd_token_client.allowance(&e.current_contract_address(), pool_id); let new_tokens = current_allowance + pool_emissions; - let new_seq = e.ledger().sequence() + 17_280 * 30; // ~30 days: TODO: check phase 1 limits + let new_seq = e.ledger().sequence() + storage::LEDGER_BUMP_USER; // ~120 days blnd_token_client.approve( &e.current_contract_address(), pool_id, &new_tokens, - &new_seq, // ~30 days: TODO: check phase 1 limits + &new_seq, ); storage::set_pool_emissions(e, pool_id, 0); pool_emissions diff --git a/backstop/src/errors.rs b/backstop/src/errors.rs index e5d3966c..46fe86c5 100644 --- a/backstop/src/errors.rs +++ b/backstop/src/errors.rs @@ -24,4 +24,5 @@ pub enum BackstopError { NotPool = 1004, InvalidShareMintAmount = 1005, InvalidTokenWithdrawAmount = 1006, + TooManyQ4WEntries = 1007, } diff --git a/backstop/src/storage.rs b/backstop/src/storage.rs index 33da90d8..112c75db 100644 --- a/backstop/src/storage.rs +++ b/backstop/src/storage.rs @@ -16,7 +16,7 @@ const LEDGER_THRESHOLD_SHARED: u32 = ONE_DAY_LEDGERS * 45; // ~ 45 days const LEDGER_BUMP_SHARED: u32 = LEDGER_THRESHOLD_SHARED + ONE_DAY_LEDGERS; // ~ 46 days const LEDGER_THRESHOLD_USER: u32 = ONE_DAY_LEDGERS * 100; // ~ 100 days -const LEDGER_BUMP_USER: u32 = LEDGER_THRESHOLD_USER + 20 * ONE_DAY_LEDGERS; // ~ 120 days +pub(crate) const LEDGER_BUMP_USER: u32 = LEDGER_THRESHOLD_USER + 20 * ONE_DAY_LEDGERS; // ~ 120 days /********** Storage Types **********/ @@ -314,8 +314,6 @@ pub fn set_last_distribution_time(e: &Env, timestamp: &u64) { } /// Get the current pool addresses that are in the reward zone -/// -// @dev - TODO: Once data access costs are available, find the breakeven point for splitting this up pub fn get_reward_zone(e: &Env) -> Vec
{ get_persistent_default( e,