diff --git a/backstop/src/backstop/user.rs b/backstop/src/backstop/user.rs index 24db4ed9..28b6488b 100644 --- a/backstop/src/backstop/user.rs +++ b/backstop/src/backstop/user.rs @@ -48,14 +48,10 @@ impl UserBalance { /// ### Errors /// If the amount to queue is greater than the available shares pub fn queue_shares_for_withdrawal(&mut self, e: &Env, to_q: i128) { - let mut q4w_amt: i128 = 0; - for q4w in self.q4w.iter() { - q4w_amt += q4w.amount - } - - if self.shares - q4w_amt < to_q { + if self.shares < to_q { panic_with_error!(e, BackstopError::InvalidBalance); } + 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 @@ -113,19 +109,6 @@ impl UserBalance { panic_with_error!(e, BackstopError::InvalidBalance); } } - - /// Withdraw shares from the user - /// - /// ### Arguments - /// * `to_q` - The amount of new shares to queue for withdraw - /// - /// ### Errors - /// If the amount to queue is greater than the available shares - pub fn withdraw_shares(&mut self, e: &Env, to_withdraw: i128) { - self.dequeue_shares_for_withdrawal(e, to_withdraw, true); - - self.shares -= to_withdraw; - } } #[cfg(test)] @@ -240,7 +223,7 @@ mod tests { }, ]; let mut user = UserBalance { - shares: 1000, + shares: 800, q4w: cur_q4w.clone(), }; @@ -281,7 +264,7 @@ mod tests { }); let to_wd = 1; - user.withdraw_shares(&e, to_wd); + user.dequeue_shares_for_withdrawal(&e, to_wd, false); } #[test] @@ -312,10 +295,10 @@ mod tests { }); let to_wd = 200; - user.withdraw_shares(&e, to_wd); + user.dequeue_shares_for_withdrawal(&e, to_wd, true); assert_eq_vec_q4w(&user.q4w, &vec![&e]); - assert_eq!(user.shares, 800); + assert_eq!(user.shares, 1000); } #[test] @@ -346,7 +329,7 @@ mod tests { }); let to_wd = 150; - user.withdraw_shares(&e, to_wd); + user.dequeue_shares_for_withdrawal(&e, to_wd, false); let expected_q4w = vec![ &e, @@ -356,7 +339,7 @@ mod tests { }, ]; assert_eq_vec_q4w(&user.q4w, &expected_q4w); - assert_eq!(user.shares, 850); + assert_eq!(user.shares, 1000); } #[test] @@ -395,7 +378,7 @@ mod tests { }); let to_wd = 300; - user.withdraw_shares(&e, to_wd); + user.dequeue_shares_for_withdrawal(&e, to_wd, true); let expected_q4w = vec![ &e, @@ -409,7 +392,7 @@ mod tests { }, ]; assert_eq_vec_q4w(&user.q4w, &expected_q4w); - assert_eq!(user.shares, 700); + assert_eq!(user.shares, 1000); } #[test] @@ -449,61 +432,7 @@ mod tests { }); let to_wd = 300; - user.withdraw_shares(&e, to_wd); - } - - #[test] - fn test_dequeue_shares() { - let e = Env::default(); - - let cur_q4w = vec![ - &e, - Q4W { - amount: 125, - exp: 10000000, - }, - Q4W { - amount: 200, - exp: 12592000, - }, - Q4W { - amount: 50, - exp: 19592000, - }, - ]; - let mut user = UserBalance { - shares: 1000, - q4w: cur_q4w.clone(), - }; - - e.ledger().set(LedgerInfo { - protocol_version: 20, - sequence_number: 1, - timestamp: 11192000, - network_id: Default::default(), - base_reserve: 10, - min_temp_entry_expiration: 10, - min_persistent_entry_expiration: 10, - max_entry_expiration: 2000000, - }); - let to_dequeue = 300; - - // verify exp is ignored if only dequeueing - user.dequeue_shares_for_withdrawal(&e, to_dequeue, false); - - let expected_q4w = vec![ - &e, - Q4W { - amount: 25, - exp: 12592000, - }, - Q4W { - amount: 50, - exp: 19592000, - }, - ]; - assert_eq_vec_q4w(&user.q4w, &expected_q4w); - assert_eq!(user.shares, 1000); + user.dequeue_shares_for_withdrawal(&e, to_wd, true); } #[test] @@ -545,7 +474,6 @@ mod tests { // verify exp is respected when specified user.dequeue_shares_for_withdrawal(&e, to_dequeue, true); } - #[test] #[should_panic(expected = "Error(Contract, #2)")] fn test_try_withdraw_shares_over_total() { diff --git a/backstop/src/backstop/withdrawal.rs b/backstop/src/backstop/withdrawal.rs index ceaa90a8..bddf32f0 100644 --- a/backstop/src/backstop/withdrawal.rs +++ b/backstop/src/backstop/withdrawal.rs @@ -1,4 +1,9 @@ -use crate::{contract::require_nonnegative, dependencies::TokenClient, emissions, storage}; +use crate::{ + contract::require_nonnegative, + dependencies::TokenClient, + emissions::{self}, + storage, +}; use soroban_sdk::{unwrap::UnwrapOptimized, Address, Env}; use super::Q4W; @@ -15,6 +20,9 @@ pub fn execute_queue_withdrawal( let mut pool_balance = storage::get_pool_balance(e, pool_address); let mut user_balance = storage::get_user_balance(e, pool_address, from); + // update emissions + emissions::update_emissions(e, pool_address, &pool_balance, from, &user_balance, false); + user_balance.queue_shares_for_withdrawal(e, amount); pool_balance.queue_for_withdraw(amount); @@ -31,7 +39,11 @@ pub fn execute_dequeue_withdrawal(e: &Env, from: &Address, pool_address: &Addres let mut pool_balance = storage::get_pool_balance(e, pool_address); let mut user_balance = storage::get_user_balance(e, pool_address, from); + // update emissions + emissions::update_emissions(e, pool_address, &pool_balance, from, &user_balance, false); + user_balance.dequeue_shares_for_withdrawal(e, amount, false); + user_balance.add_shares(amount); pool_balance.dequeue_q4w(e, amount); storage::set_user_balance(e, pool_address, from, &user_balance); @@ -45,9 +57,7 @@ pub fn execute_withdraw(e: &Env, from: &Address, pool_address: &Address, amount: let mut pool_balance = storage::get_pool_balance(e, pool_address); let mut user_balance = storage::get_user_balance(e, pool_address, from); - emissions::update_emissions(e, pool_address, &pool_balance, from, &user_balance, false); - - user_balance.withdraw_shares(e, amount); + user_balance.dequeue_shares_for_withdrawal(e, amount, true); let to_return = pool_balance.convert_to_tokens(amount); pool_balance.withdraw(e, to_return, amount); @@ -108,7 +118,7 @@ mod tests { execute_queue_withdrawal(&e, &samwise, &pool_address, 42_0000000); let new_user_balance = storage::get_user_balance(&e, &pool_address, &samwise); - assert_eq!(new_user_balance.shares, 100_0000000); + assert_eq!(new_user_balance.shares, 58_0000000); let expected_q4w = vec![ &e, Q4W { @@ -213,7 +223,7 @@ mod tests { execute_dequeue_withdrawal(&e, &samwise, &pool_address, 30_0000000); let new_user_balance = storage::get_user_balance(&e, &pool_address, &samwise); - assert_eq!(new_user_balance.shares, 75_0000000); + assert_eq!(new_user_balance.shares, 40_0000000); let expected_q4w = vec![ &e, Q4W { diff --git a/backstop/src/emissions/claim.rs b/backstop/src/emissions/claim.rs index b4f940d2..37b056f2 100644 --- a/backstop/src/emissions/claim.rs +++ b/backstop/src/emissions/claim.rs @@ -145,11 +145,11 @@ mod tests { &vec![&e, pool_1_id.clone(), pool_2_id.clone()], &frodo, ); - assert_eq!(result, 75_3145677 + 5_0250000); - assert_eq!(blnd_token_client.balance(&frodo), 75_3145677 + 5_0250000); + assert_eq!(result, 75_3145677 + 6_2904190); + assert_eq!(blnd_token_client.balance(&frodo), 75_3145677 + 6_2904190); assert_eq!( blnd_token_client.balance(&backstop_address), - 100_0000000 - (75_3145677 + 5_0250000) + 100_0000000 - (75_3145677 + 6_2904190) ); let new_backstop_1_data = @@ -157,18 +157,18 @@ mod tests { let new_user_1_data = storage::get_user_emis_data(&e, &pool_1_id, &samwise).unwrap_optimized(); assert_eq!(new_backstop_1_data.last_time, block_timestamp); - assert_eq!(new_backstop_1_data.index, 82322222); + assert_eq!(new_backstop_1_data.index, 83434384); assert_eq!(new_user_1_data.accrued, 0); - assert_eq!(new_user_1_data.index, 82322222); + assert_eq!(new_user_1_data.index, 83434384); let new_backstop_2_data = storage::get_backstop_emis_data(&e, &pool_2_id).unwrap_optimized(); let new_user_2_data = storage::get_user_emis_data(&e, &pool_2_id, &samwise).unwrap_optimized(); assert_eq!(new_backstop_2_data.last_time, block_timestamp); - assert_eq!(new_backstop_2_data.index, 6700000); + assert_eq!(new_backstop_2_data.index, 7052631); assert_eq!(new_user_2_data.accrued, 0); - assert_eq!(new_user_2_data.index, 6700000); + assert_eq!(new_user_2_data.index, 7052631); }); } @@ -276,11 +276,11 @@ mod tests { &vec![&e, pool_1_id.clone(), pool_2_id.clone()], &frodo, ); - assert_eq!(result, 75_3145677 + 5_0250000); - assert_eq!(blnd_token_client.balance(&frodo), 75_3145677 + 5_0250000); + assert_eq!(result, 75_3145677 + 6_2904190); + assert_eq!(blnd_token_client.balance(&frodo), 75_3145677 + 6_2904190); assert_eq!( blnd_token_client.balance(&backstop_address), - 200_0000000 - (75_3145677 + 5_0250000) + 200_0000000 - (75_3145677 + 6_2904190) ); let new_backstop_1_data = @@ -288,18 +288,18 @@ mod tests { let new_user_1_data = storage::get_user_emis_data(&e, &pool_1_id, &samwise).unwrap_optimized(); assert_eq!(new_backstop_1_data.last_time, block_timestamp); - assert_eq!(new_backstop_1_data.index, 82322222); + assert_eq!(new_backstop_1_data.index, 83434384); assert_eq!(new_user_1_data.accrued, 0); - assert_eq!(new_user_1_data.index, 82322222); + assert_eq!(new_user_1_data.index, 83434384); let new_backstop_2_data = storage::get_backstop_emis_data(&e, &pool_2_id).unwrap_optimized(); let new_user_2_data = storage::get_user_emis_data(&e, &pool_2_id, &samwise).unwrap_optimized(); assert_eq!(new_backstop_2_data.last_time, block_timestamp); - assert_eq!(new_backstop_2_data.index, 6700000); + assert_eq!(new_backstop_2_data.index, 7052631); assert_eq!(new_user_2_data.accrued, 0); - assert_eq!(new_user_2_data.index, 6700000); + assert_eq!(new_user_2_data.index, 7052631); let block_timestamp_1 = 1500000000 + 12345 + 12345; e.ledger().set(LedgerInfo { @@ -318,14 +318,14 @@ mod tests { &vec![&e, pool_1_id.clone(), pool_2_id.clone()], &frodo, ); - assert_eq!(result_1, 1005235710); + assert_eq!(result_1, 1029168100); assert_eq!( blnd_token_client.balance(&frodo), - 75_3145677 + 5_0250000 + 1005235710 + 75_3145677 + 6_2904190 + 1029168100 ); assert_eq!( blnd_token_client.balance(&backstop_address), - 200_0000000 - (75_3145677 + 5_0250000) - (1005235710) + 200_0000000 - (75_3145677 + 6_2904190) - (1029168100) ); let new_backstop_1_data = @@ -333,18 +333,18 @@ mod tests { let new_user_1_data = storage::get_user_emis_data(&e, &pool_1_id, &samwise).unwrap_optimized(); assert_eq!(new_backstop_1_data.last_time, block_timestamp_1); - assert_eq!(new_backstop_1_data.index, 164622222); + assert_eq!(new_backstop_1_data.index, 166846546); assert_eq!(new_user_1_data.accrued, 0); - assert_eq!(new_user_1_data.index, 164622222); + assert_eq!(new_user_1_data.index, 166846546); let new_backstop_2_data = storage::get_backstop_emis_data(&e, &pool_2_id).unwrap_optimized(); let new_user_2_data = storage::get_user_emis_data(&e, &pool_2_id, &samwise).unwrap_optimized(); assert_eq!(new_backstop_2_data.last_time, block_timestamp_1); - assert_eq!(new_backstop_2_data.index, 41971428); + assert_eq!(new_backstop_2_data.index, 44180450); assert_eq!(new_user_2_data.accrued, 0); - assert_eq!(new_user_2_data.index, 41971428); + assert_eq!(new_user_2_data.index, 44180450); }); } diff --git a/backstop/src/emissions/distributor.rs b/backstop/src/emissions/distributor.rs index a7c80f6e..a75674c5 100644 --- a/backstop/src/emissions/distributor.rs +++ b/backstop/src/emissions/distributor.rs @@ -55,7 +55,7 @@ pub fn update_emission_data( }; let additional_idx = (i128(max_timestamp - emis_data.last_time) * i128(emis_config.eps)) - .fixed_div_floor(pool_balance.shares, SCALAR_7) + .fixed_div_floor(pool_balance.shares - pool_balance.q4w, SCALAR_7) .unwrap_optimized(); let new_data = BackstopEmissionsData { index: additional_idx + emis_data.index, @@ -77,8 +77,7 @@ fn update_user_emissions( if user_data.index != emis_data.index || to_claim { let mut accrual = user_data.accrued; if user_balance.shares != 0 { - let to_accrue = user_balance - .shares + let to_accrue = (user_balance.shares) .fixed_mul_floor(emis_data.index - user_data.index, SCALAR_7) .unwrap_optimized(); accrual += to_accrue; @@ -119,7 +118,7 @@ fn set_user_emissions( #[cfg(test)] mod tests { use crate::{ - constants::BACKSTOP_EPOCH, storage::BackstopEmissionConfig, testutils::create_backstop, + constants::BACKSTOP_EPOCH, storage::BackstopEmissionConfig, testutils::create_backstop, Q4W, }; use super::*; @@ -406,4 +405,68 @@ mod tests { assert_eq!(new_user_data.index, 34566000); }); } + #[test] + fn test_update_emissions_q4w_not_counted() { + let e = Env::default(); + let block_timestamp = BACKSTOP_EPOCH + 1234; + e.ledger().set(LedgerInfo { + timestamp: block_timestamp, + protocol_version: 20, + sequence_number: 0, + network_id: Default::default(), + base_reserve: 10, + min_temp_entry_expiration: 10, + min_persistent_entry_expiration: 10, + max_entry_expiration: 2000000, + }); + + let backstop_id = create_backstop(&e); + let pool_1 = Address::random(&e); + let samwise = Address::random(&e); + + let backstop_emissions_config = BackstopEmissionConfig { + expiration: BACKSTOP_EPOCH + 7 * 24 * 60 * 60, + eps: 0_1000000, + }; + let backstop_emissions_data = BackstopEmissionsData { + index: 22222, + last_time: BACKSTOP_EPOCH, + }; + let user_emissions_data = UserEmissionData { + index: 11111, + accrued: 3, + }; + e.as_contract(&backstop_id, || { + storage::set_next_emission_cycle(&e, &(BACKSTOP_EPOCH + 7 * 24 * 60 * 60)); + storage::set_backstop_emis_config(&e, &pool_1, &backstop_emissions_config); + storage::set_backstop_emis_data(&e, &pool_1, &backstop_emissions_data); + storage::set_user_emis_data(&e, &pool_1, &samwise, &user_emissions_data); + + let pool_balance = PoolBalance { + shares: 150_0000000, + tokens: 200_0000000, + q4w: 4_5000000, + }; + let q4w: Q4W = Q4W { + amount: (4_5000000), + exp: (5000), + }; + let user_balance = UserBalance { + shares: 4_5000000, + q4w: vec![&e, q4w], + }; + + let result = + update_emissions(&e, &pool_1, &pool_balance, &samwise, &user_balance, false); + + let new_backstop_data = storage::get_backstop_emis_data(&e, &pool_1).unwrap_optimized(); + let new_user_data = + storage::get_user_emis_data(&e, &pool_1, &samwise).unwrap_optimized(); + assert_eq!(result, 0); + assert_eq!(new_backstop_data.last_time, block_timestamp); + assert_eq!(new_backstop_data.index, 8503321); + assert_eq!(new_user_data.accrued, 38214948); + assert_eq!(new_user_data.index, 8503321); + }); + } } diff --git a/backstop/src/emissions/manager.rs b/backstop/src/emissions/manager.rs index a78f5086..6a58d019 100644 --- a/backstop/src/emissions/manager.rs +++ b/backstop/src/emissions/manager.rs @@ -75,16 +75,18 @@ pub fn update_emission_cycle(e: &Env) { let mut total_tokens: i128 = 0; for rz_pool_index in 0..rz_len { let rz_pool = reward_zone.get(rz_pool_index).unwrap_optimized(); - let pool_tokens = storage::get_pool_balance(e, &rz_pool).tokens; - rz_tokens.push_back(pool_tokens); - total_tokens += i128(pool_tokens); + let mut pool_balance = storage::get_pool_balance(e, &rz_pool); + let net_deposits = + pool_balance.tokens.clone() - pool_balance.convert_to_tokens(pool_balance.q4w.clone()); + rz_tokens.push_back(net_deposits); + total_tokens += net_deposits; } let blnd_token_client = TokenClient::new(e, &storage::get_blnd_token(e)); // store pools EPS and distribute emissions to backstop depositors for rz_pool_index in 0..rz_len { let rz_pool = reward_zone.get(rz_pool_index).unwrap_optimized(); - let cur_pool_tokens = i128(rz_tokens.pop_front_unchecked()); + let cur_pool_tokens = rz_tokens.pop_front_unchecked(); let share = cur_pool_tokens .fixed_div_floor(total_tokens, SCALAR_7) .unwrap_optimized(); diff --git a/test-suites/tests/test_backstop.rs b/test-suites/tests/test_backstop.rs index a6283e07..78311902 100644 --- a/test-suites/tests/test_backstop.rs +++ b/test-suites/tests/test_backstop.rs @@ -434,20 +434,27 @@ fn test_backstop() { } ) ); - let emitted_tokens = (28 * 24 * 60 * 60 - 61 * 60) * SCALAR_7; // 27d22hr59m on emissions to claim - let emission_share = 0_7000000.fixed_mul_floor(0_2000000, SCALAR_7).unwrap(); - let emitted_blnd = emission_share - .fixed_mul_floor(emitted_tokens, SCALAR_7) + //6d23hr at full + //7 days at none + //7 at 6250 + (60 * 60 * 24 * 16 + 1) + let emission_share_1 = 0_7000000.fixed_mul_floor(0_2000000, SCALAR_7).unwrap(); + let emission_share_2 = 0_7000000.fixed_mul_floor(0_1111111, SCALAR_7).unwrap(); + let emitted_blnd_1 = ((7 * 24 * 60 * 60 - 61 * 60) * SCALAR_7) + .fixed_mul_floor(emission_share_1, SCALAR_7) .unwrap(); + let emitted_blnd_2 = ((14 * 24 * 60 * 60 + 1) * SCALAR_7 + 2096022) + .fixed_mul_floor(emission_share_2, SCALAR_7) + .unwrap(); + assert_approx_eq_abs( fixture.tokens[TokenIndex::BLND].balance(&sam), - sam_blnd_balance + emitted_blnd, - 100, + sam_blnd_balance + emitted_blnd_1 + emitted_blnd_2, + SCALAR_7, ); assert_approx_eq_abs( fixture.tokens[TokenIndex::BLND].balance(&fixture.backstop.address), - bstop_blend_balance - emitted_blnd, - 100, + bstop_blend_balance - emitted_blnd_1 - emitted_blnd_2, + SCALAR_7, ); let event = vec![&fixture.env, fixture.env.events().all().last_unchecked()]; assert_eq!( @@ -457,7 +464,7 @@ fn test_backstop() { ( fixture.backstop.address.clone(), (Symbol::new(&fixture.env, "claim"), sam.clone()).into_val(&fixture.env), - emitted_blnd.into_val(&fixture.env), + (emitted_blnd_1 + emitted_blnd_2).into_val(&fixture.env), ) ] );