diff --git a/pool-factory/src/test.rs b/pool-factory/src/test.rs index a2aa2249..aabe5228 100644 --- a/pool-factory/src/test.rs +++ b/pool-factory/src/test.rs @@ -91,7 +91,7 @@ fn test_pool_factory() { pool::PoolConfig { oracle: oracle, bstop_rate: backstop_rate, - status: 1 + status: 3 } ); assert_eq!( diff --git a/pool/src/contract.rs b/pool/src/contract.rs index 49822f93..eb7f1dfc 100644 --- a/pool/src/contract.rs +++ b/pool/src/contract.rs @@ -119,28 +119,35 @@ pub trait Pool { /// If the user has collateral posted fn bad_debt(e: Env, user: Address); - /// Update the pool status based on the backstop state - /// * 0 = active - if the minimum backstop deposit has been reached - /// * 1 = on ice - if the minimum backstop deposit has not been reached - /// or 25% of backstop deposits are queued for withdrawal - /// * 2 = frozen - if 50% of backstop deposits are queued for withdrawal + /// Update the pool status based on the backstop state - backstop triggered status' are odd numbers + /// * 1 = backstop active - if the minimum backstop deposit has been reached + /// and 30% of backstop deposits are not queued for withdrawal + /// then all pool operations are permitted + /// * 3 = backstop on-ice - if the minimum backstop deposit has not been reached + /// or 30% of backstop deposits are queued for withdrawal and admin active isn't set + /// or 50% of backstop deposits are queued for withdrawal + /// then borrowing and cancelling liquidations are not permitted + /// * 5 = backstop frozen - if 60% of backstop deposits are queued for withdrawal and admin on-ice isn't set + /// or 75% of backstop deposits are queued for withdrawal + /// then all borrowing, cancelling liquidations, and supplying are not permitted /// /// ### Panics - /// If the pool is currently of status 3, "admin-freeze", where only the admin + /// If the pool is currently on status 4, "admin-freeze", where only the admin /// can perform a status update via `set_status` fn update_status(e: Env) -> u32; /// (Admin only) Pool status is changed to "pool_status" - /// * 0 = active - /// * 1 = on ice - /// * 2 = frozen - /// * 3 = admin frozen (only the admin can unfreeze) + /// * 0 = admin active - requires that the backstop threshold is met + /// and less than 50% of backstop deposits are queued for withdrawal + /// * 2 = admin on-ice - requires that less than 75% of backstop deposits are queued for withdrawal + /// * 4 = admin frozen - can always be set /// /// ### Arguments /// * 'pool_status' - The pool status to be set /// /// ### Panics /// If the caller is not the admin + /// If the specified conditions are not met for the status to be set fn set_status(e: Env, pool_status: u32); /********* Emission Functions **********/ @@ -314,9 +321,7 @@ impl Pool for PoolContract { storage::extend_instance(&e); let admin = storage::get_admin(&e); admin.require_auth(); - - pool::set_pool_status(&e, pool_status); - + pool::execute_set_pool_status(&e, pool_status); e.events() .publish((Symbol::new(&e, "set_status"), admin), pool_status); } diff --git a/pool/src/errors.rs b/pool/src/errors.rs index a3d83b5f..6c89b029 100644 --- a/pool/src/errors.rs +++ b/pool/src/errors.rs @@ -12,6 +12,7 @@ pub enum PoolError { NegativeAmount = 4, InvalidPoolInitArgs = 5, InvalidReserveMetadata = 6, + StatusNotAllowed = 8, // Pool State Errors (10-19) InvalidHf = 10, InvalidPoolStatus = 11, diff --git a/pool/src/pool/config.rs b/pool/src/pool/config.rs index c560db04..b12a3005 100644 --- a/pool/src/pool/config.rs +++ b/pool/src/pool/config.rs @@ -37,7 +37,7 @@ pub fn execute_initialize( &PoolConfig { oracle: oracle.clone(), bstop_rate: *bstop_rate, - status: 1, + status: 3, }, ); storage::set_blnd_token(e, blnd_id); @@ -160,7 +160,7 @@ mod tests { let pool_config = storage::get_pool_config(&e); assert_eq!(pool_config.oracle, oracle); assert_eq!(pool_config.bstop_rate, bstop_rate); - assert_eq!(pool_config.status, 1); + assert_eq!(pool_config.status, 3); assert_eq!(storage::get_backstop(&e), backstop_address); assert_eq!(storage::get_blnd_token(&e), blnd_id); assert_eq!(storage::get_usdc_token(&e), usdc_id); diff --git a/pool/src/pool/mod.rs b/pool/src/pool/mod.rs index 5f7ef778..83adf8e4 100644 --- a/pool/src/pool/mod.rs +++ b/pool/src/pool/mod.rs @@ -29,4 +29,6 @@ mod user; pub use user::{Positions, User}; mod status; -pub use status::{calc_pool_backstop_threshold, execute_update_pool_status, set_pool_status}; +pub use status::{ + calc_pool_backstop_threshold, execute_set_pool_status, execute_update_pool_status, +}; diff --git a/pool/src/pool/pool.rs b/pool/src/pool/pool.rs index 682e1f08..f1cef37d 100644 --- a/pool/src/pool/pool.rs +++ b/pool/src/pool/pool.rs @@ -67,9 +67,9 @@ impl Pool { /// ### Arguments /// * `action_type` - The type of action being performed pub fn require_action_allowed(&self, e: &Env, action_type: u32) { - // disable borrowing for any non-active pool and disable supplying for any frozen pool - if (self.config.status > 0 && action_type == 4) - || (self.config.status > 1 && (action_type == 2 || action_type == 0)) + // disable borrowing or auction cancellation for any non-active pool and disable supplying for any frozen pool + if (self.config.status > 1 && (action_type == 4 || action_type == 9)) + || (self.config.status > 3 && (action_type == 2 || action_type == 0)) { panic_with_error!(e, PoolError::InvalidPoolStatus); } @@ -267,7 +267,7 @@ mod tests { let pool_config = PoolConfig { oracle, bstop_rate: 0_200_000_000, - status: 1, + status: 2, }; e.as_contract(&pool, || { storage::set_pool_config(&e, &pool_config); @@ -286,7 +286,7 @@ mod tests { let pool_config = PoolConfig { oracle, bstop_rate: 0_200_000_000, - status: 0, + status: 1, }; e.as_contract(&pool, || { storage::set_pool_config(&e, &pool_config); @@ -298,7 +298,7 @@ mod tests { #[test] #[should_panic(expected = "Error(Contract, #11)")] - fn test_require_action_allowed_supply_while_frozen() { + fn test_require_action_allowed_cancel_liquidation_while_on_ice_panics() { let e = Env::default(); let pool = testutils::create_pool(&e); @@ -312,6 +312,45 @@ mod tests { storage::set_pool_config(&e, &pool_config); let pool = Pool::load(&e); + pool.require_action_allowed(&e, 9); + }); + } + + #[test] + fn test_require_action_allowed_cancel_liquidation_while_active() { + let e = Env::default(); + + let pool = testutils::create_pool(&e); + let oracle = Address::generate(&e); + let pool_config = PoolConfig { + oracle, + bstop_rate: 0_200_000_000, + status: 1, + }; + e.as_contract(&pool, || { + storage::set_pool_config(&e, &pool_config); + let pool = Pool::load(&e); + + pool.require_action_allowed(&e, 9); + }); + } + + #[test] + #[should_panic(expected = "Error(Contract, #11)")] + fn test_require_action_allowed_supply_while_frozen() { + let e = Env::default(); + + let pool = testutils::create_pool(&e); + let oracle = Address::generate(&e); + let pool_config = PoolConfig { + oracle, + bstop_rate: 0_200_000_000, + status: 4, + }; + e.as_contract(&pool, || { + storage::set_pool_config(&e, &pool_config); + let pool = Pool::load(&e); + pool.require_action_allowed(&e, 0); }); } @@ -326,7 +365,7 @@ mod tests { let pool_config = PoolConfig { oracle, bstop_rate: 0_200_000_000, - status: 2, + status: 4, }; e.as_contract(&pool, || { storage::set_pool_config(&e, &pool_config); @@ -345,7 +384,7 @@ mod tests { let pool_config = PoolConfig { oracle, bstop_rate: 0_200_000_000, - status: 2, + status: 4, }; e.as_contract(&pool, || { storage::set_pool_config(&e, &pool_config); diff --git a/pool/src/pool/status.rs b/pool/src/pool/status.rs index 865f2d92..283d8da0 100644 --- a/pool/src/pool/status.rs +++ b/pool/src/pool/status.rs @@ -1,8 +1,7 @@ use crate::{ constants::SCALAR_7, dependencies::{BackstopClient, PoolBackstopData}, - errors::PoolError, - storage, + storage, PoolError, }; use soroban_sdk::{panic_with_error, Env}; @@ -11,46 +10,99 @@ use soroban_sdk::{panic_with_error, Env}; #[allow(clippy::inconsistent_digit_grouping)] pub fn execute_update_pool_status(e: &Env) -> u32 { let mut pool_config = storage::get_pool_config(e); - if pool_config.status > 2 { - // pool has been admin frozen and can only be restored by the admin - panic_with_error!(e, PoolError::InvalidPoolStatus); - } + // check the pool has met minimum backstop deposits let backstop_id = storage::get_backstop(e); let backstop_client = BackstopClient::new(e, &backstop_id); let pool_backstop_data = backstop_client.pool_data(&e.current_contract_address()); - if pool_backstop_data.q4w_pct >= 0_5000000 { - pool_config.status = 2; - } else if pool_backstop_data.q4w_pct >= 0_2500000 - || calc_pool_backstop_threshold(&pool_backstop_data) < SCALAR_7 - { - pool_config.status = 1; - } else { - pool_config.status = 0; + let threshold = calc_pool_backstop_threshold(&pool_backstop_data); + let mut met_threshold = true; + if threshold < SCALAR_7 { + met_threshold = false; } - storage::set_pool_config(e, &pool_config); + match pool_config.status { + // Admin frozen + 4 => { + // Admin frozen supersedes all other statuses + panic_with_error!(e, PoolError::StatusNotAllowed); + } + // Admin on-ice + 2 => { + if pool_backstop_data.q4w_pct >= 0_7500000 { + // Q4W over 75% freezes the pool + pool_config.status = 5; + } + } + // Admin active + 0 => { + if !met_threshold || pool_backstop_data.q4w_pct >= 0_5000000 { + // Q4w over 50% or being under threshold puts the pool on-ice + pool_config.status = 3; + } + } + // Admin status isn't set + _ => { + if pool_backstop_data.q4w_pct >= 0_6000000 { + // Q4w over 60% freezes the pool + pool_config.status = 5; + } else if pool_backstop_data.q4w_pct >= 0_3000000 || !met_threshold { + // Q4w over 30% or being under threshold puts the pool on-ice + pool_config.status = 3; + } else { + // Backstop is healthy and the pool can be activated + pool_config.status = 1; + } + } + } + storage::set_pool_config(e, &pool_config); pool_config.status } -/// Update the pool status +/// Admin set the pool status +#[allow(clippy::zero_prefixed_literal)] #[allow(clippy::inconsistent_digit_grouping)] -pub fn set_pool_status(e: &Env, pool_status: u32) { - if pool_status == 0 { - // check the pool has met minimum backstop deposits before being turned on - let backstop_id = storage::get_backstop(e); - let backstop_client = BackstopClient::new(e, &backstop_id); - - let pool_backstop_data = backstop_client.pool_data(&e.current_contract_address()); - let threshold = calc_pool_backstop_threshold(&pool_backstop_data); - if threshold < SCALAR_7 { - panic_with_error!(e, PoolError::InvalidPoolStatus); +pub fn execute_set_pool_status(e: &Env, pool_status: u32) { + let mut pool_config = storage::get_pool_config(e); + + // check the pool has met minimum backstop deposits + let backstop_id = storage::get_backstop(e); + let backstop_client = BackstopClient::new(e, &backstop_id); + + let pool_backstop_data = backstop_client.pool_data(&e.current_contract_address()); + // Admins cannot set non-admin status' + if pool_status % 2 != 0 { + panic_with_error!(e, PoolError::BadRequest); + } + match pool_status { + 0 => { + // Threshold must be met and q4w must be under 50% for the admin to set Active + if calc_pool_backstop_threshold(&pool_backstop_data) < SCALAR_7 + || pool_backstop_data.q4w_pct >= 0_5000000 + { + panic_with_error!(e, PoolError::StatusNotAllowed); + } + // Admin Active + pool_config.status = 0; + } + 2 => { + // Q4w must be under 75% for admin to set On-Ice + if pool_backstop_data.q4w_pct >= 0_7500000 { + panic_with_error!(e, PoolError::StatusNotAllowed); + } + // Admin On-Ice + pool_config.status = 2; + } + 4 => { + // Admin can always freeze the pool + // Admin Frozen + pool_config.status = 4; + } + _ => { + panic_with_error!(e, PoolError::BadRequest); } } - - let mut pool_config = storage::get_pool_config(e); - pool_config.status = pool_status; storage::set_pool_config(e, &pool_config); } @@ -98,7 +150,7 @@ mod tests { use soroban_sdk::{testutils::Address as _, vec, Address}; #[test] - fn test_set_pool_status() { + fn test_set_pool_status_active() { let e = Env::default(); e.budget().reset_unlimited(); e.mock_all_auths_allowing_non_root_auth(); @@ -136,7 +188,7 @@ mod tests { storage::set_admin(&e, &bombadil); storage::set_pool_config(&e, &pool_config); - set_pool_status(&e, 0); + execute_set_pool_status(&e, 0); let new_pool_config = storage::get_pool_config(&e); assert_eq!(new_pool_config.status, 0); @@ -144,8 +196,8 @@ mod tests { } #[test] - #[should_panic(expected = "Error(Contract, #11)")] - fn test_set_pool_status_blocks_without_backstop_minimum() { + #[should_panic(expected = "Error(Contract, #8)")] + fn test_set_pool_status_active_blocks_without_backstop_minimum() { let e = Env::default(); e.budget().reset_unlimited(); e.mock_all_auths_allowing_non_root_auth(); @@ -183,12 +235,146 @@ mod tests { storage::set_admin(&e, &bombadil); storage::set_pool_config(&e, &pool_config); - set_pool_status(&e, 0); + execute_set_pool_status(&e, 0); }); } #[test] - fn test_update_pool_status_active() { + #[should_panic(expected = "Error(Contract, #8)")] + fn test_set_pool_status_active_blocks_with_too_high_q4w() { + let e = Env::default(); + e.budget().reset_unlimited(); + e.mock_all_auths_allowing_non_root_auth(); + let pool_id = create_pool(&e); + let oracle_id = Address::generate(&e); + + let bombadil = Address::generate(&e); + let samwise = Address::generate(&e); + + let (blnd, blnd_client) = create_token_contract(&e, &bombadil); + let (usdc, usdc_client) = create_token_contract(&e, &bombadil); + let (lp_token, lp_token_client) = create_comet_lp_pool(&e, &bombadil, &blnd, &usdc); + let (backstop_id, backstop_client) = create_backstop(&e); + setup_backstop(&e, &pool_id, &backstop_id, &lp_token, &usdc, &blnd); + + // mint lp tokens + blnd_client.mint(&samwise, &500_001_0000000); + blnd_client.approve(&samwise, &lp_token, &i128::MAX, &99999); + usdc_client.mint(&samwise, &12_501_0000000); + usdc_client.approve(&samwise, &lp_token, &i128::MAX, &99999); + lp_token_client.join_pool( + &50_000_0000000, + &vec![&e, 500_001_0000000, 12_501_0000000], + &samwise, + ); + backstop_client.deposit(&samwise, &pool_id, &50_000_0000000); + backstop_client.update_tkn_val(); + backstop_client.queue_withdrawal(&samwise, &pool_id, &30_000_0000000); + + let pool_config = PoolConfig { + oracle: oracle_id, + bstop_rate: 0, + status: 2, + }; + e.as_contract(&pool_id, || { + storage::set_admin(&e, &bombadil); + storage::set_pool_config(&e, &pool_config); + + execute_set_pool_status(&e, 0); + }); + } + #[test] + fn test_set_pool_status_on_ice() { + let e = Env::default(); + e.budget().reset_unlimited(); + e.mock_all_auths_allowing_non_root_auth(); + let pool_id = create_pool(&e); + let oracle_id = Address::generate(&e); + + let bombadil = Address::generate(&e); + let samwise = Address::generate(&e); + + let (blnd, blnd_client) = create_token_contract(&e, &bombadil); + let (usdc, usdc_client) = create_token_contract(&e, &bombadil); + let (lp_token, lp_token_client) = create_comet_lp_pool(&e, &bombadil, &blnd, &usdc); + let (backstop_id, backstop_client) = create_backstop(&e); + setup_backstop(&e, &pool_id, &backstop_id, &lp_token, &usdc, &blnd); + + // mint lp tokens + blnd_client.mint(&samwise, &500_001_0000000); + blnd_client.approve(&samwise, &lp_token, &i128::MAX, &99999); + usdc_client.mint(&samwise, &12_501_0000000); + usdc_client.approve(&samwise, &lp_token, &i128::MAX, &99999); + lp_token_client.join_pool( + &50_000_0000000, + &vec![&e, 500_001_0000000, 12_501_0000000], + &samwise, + ); + backstop_client.deposit(&samwise, &pool_id, &50_000_0000000); + backstop_client.update_tkn_val(); + + let pool_config = PoolConfig { + oracle: oracle_id, + bstop_rate: 0, + status: 1, + }; + e.as_contract(&pool_id, || { + storage::set_admin(&e, &bombadil); + storage::set_pool_config(&e, &pool_config); + + execute_set_pool_status(&e, 2); + + let new_pool_config = storage::get_pool_config(&e); + assert_eq!(new_pool_config.status, 2); + }); + } + + #[test] + #[should_panic(expected = "Error(Contract, #8)")] + fn test_set_pool_status_on_ice_blocks_with_too_high_q4w() { + let e = Env::default(); + e.budget().reset_unlimited(); + e.mock_all_auths_allowing_non_root_auth(); + let pool_id = create_pool(&e); + let oracle_id = Address::generate(&e); + + let bombadil = Address::generate(&e); + let samwise = Address::generate(&e); + + let (blnd, blnd_client) = create_token_contract(&e, &bombadil); + let (usdc, usdc_client) = create_token_contract(&e, &bombadil); + let (lp_token, lp_token_client) = create_comet_lp_pool(&e, &bombadil, &blnd, &usdc); + let (backstop_id, backstop_client) = create_backstop(&e); + setup_backstop(&e, &pool_id, &backstop_id, &lp_token, &usdc, &blnd); + + // mint lp tokens + blnd_client.mint(&samwise, &500_001_0000000); + blnd_client.approve(&samwise, &lp_token, &i128::MAX, &99999); + usdc_client.mint(&samwise, &12_501_0000000); + usdc_client.approve(&samwise, &lp_token, &i128::MAX, &99999); + lp_token_client.join_pool( + &50_000_0000000, + &vec![&e, 500_001_0000000, 12_501_0000000], + &samwise, + ); + backstop_client.deposit(&samwise, &pool_id, &50_000_0000000); + backstop_client.update_tkn_val(); + backstop_client.queue_withdrawal(&samwise, &pool_id, &40_000_0000000); + + let pool_config = PoolConfig { + oracle: oracle_id, + bstop_rate: 0, + status: 2, + }; + e.as_contract(&pool_id, || { + storage::set_admin(&e, &bombadil); + storage::set_pool_config(&e, &pool_config); + + execute_set_pool_status(&e, 2); + }); + } + #[test] + fn test_set_pool_status_frozen() { let e = Env::default(); e.budget().reset_unlimited(); e.mock_all_auths_allowing_non_root_auth(); @@ -226,6 +412,142 @@ mod tests { storage::set_admin(&e, &bombadil); storage::set_pool_config(&e, &pool_config); + execute_set_pool_status(&e, 4); + + let new_pool_config = storage::get_pool_config(&e); + assert_eq!(new_pool_config.status, 4); + }); + } + #[test] + #[should_panic(expected = "Error(Contract, #2)")] + fn test_set_non_admin_pool_status_panics() { + let e = Env::default(); + e.budget().reset_unlimited(); + e.mock_all_auths_allowing_non_root_auth(); + let pool_id = create_pool(&e); + let oracle_id = Address::generate(&e); + + let bombadil = Address::generate(&e); + let samwise = Address::generate(&e); + + let (blnd, blnd_client) = create_token_contract(&e, &bombadil); + let (usdc, usdc_client) = create_token_contract(&e, &bombadil); + let (lp_token, lp_token_client) = create_comet_lp_pool(&e, &bombadil, &blnd, &usdc); + let (backstop_id, backstop_client) = create_backstop(&e); + setup_backstop(&e, &pool_id, &backstop_id, &lp_token, &usdc, &blnd); + + // mint lp tokens + blnd_client.mint(&samwise, &500_001_0000000); + blnd_client.approve(&samwise, &lp_token, &i128::MAX, &99999); + usdc_client.mint(&samwise, &12_501_0000000); + usdc_client.approve(&samwise, &lp_token, &i128::MAX, &99999); + lp_token_client.join_pool( + &50_000_0000000, + &vec![&e, 500_001_0000000, 12_501_0000000], + &samwise, + ); + backstop_client.deposit(&samwise, &pool_id, &50_000_0000000); + backstop_client.update_tkn_val(); + + let pool_config = PoolConfig { + oracle: oracle_id, + bstop_rate: 0, + status: 2, + }; + e.as_contract(&pool_id, || { + storage::set_admin(&e, &bombadil); + storage::set_pool_config(&e, &pool_config); + + execute_set_pool_status(&e, 1); + }); + } + + #[test] + fn test_update_pool_status_active() { + let e = Env::default(); + e.budget().reset_unlimited(); + e.mock_all_auths_allowing_non_root_auth(); + let pool_id = create_pool(&e); + let oracle_id = Address::generate(&e); + + let bombadil = Address::generate(&e); + let samwise = Address::generate(&e); + + let (blnd, blnd_client) = create_token_contract(&e, &bombadil); + let (usdc, usdc_client) = create_token_contract(&e, &bombadil); + let (lp_token, lp_token_client) = create_comet_lp_pool(&e, &bombadil, &blnd, &usdc); + let (backstop_id, backstop_client) = create_backstop(&e); + setup_backstop(&e, &pool_id, &backstop_id, &lp_token, &usdc, &blnd); + + // mint lp tokens + blnd_client.mint(&samwise, &500_001_0000000); + blnd_client.approve(&samwise, &lp_token, &i128::MAX, &99999); + usdc_client.mint(&samwise, &12_501_0000000); + usdc_client.approve(&samwise, &lp_token, &i128::MAX, &99999); + lp_token_client.join_pool( + &50_000_0000000, + &vec![&e, 500_001_0000000, 12_501_0000000], + &samwise, + ); + backstop_client.deposit(&samwise, &pool_id, &50_000_0000000); + backstop_client.update_tkn_val(); + + let pool_config = PoolConfig { + oracle: oracle_id, + bstop_rate: 0, + status: 3, + }; + e.as_contract(&pool_id, || { + storage::set_admin(&e, &bombadil); + storage::set_pool_config(&e, &pool_config); + + let status = execute_update_pool_status(&e); + + let new_pool_config = storage::get_pool_config(&e); + assert_eq!(new_pool_config.status, status); + assert_eq!(status, 1); + }); + } + + #[test] + fn test_update_pool_status_admin_set_no_changes() { + let e = Env::default(); + e.budget().reset_unlimited(); + e.mock_all_auths_allowing_non_root_auth(); + let pool_id = create_pool(&e); + let oracle_id = Address::generate(&e); + + let bombadil = Address::generate(&e); + let samwise = Address::generate(&e); + + let (blnd, blnd_client) = create_token_contract(&e, &bombadil); + let (usdc, usdc_client) = create_token_contract(&e, &bombadil); + let (lp_token, lp_token_client) = create_comet_lp_pool(&e, &bombadil, &blnd, &usdc); + let (backstop_id, backstop_client) = create_backstop(&e); + setup_backstop(&e, &pool_id, &backstop_id, &lp_token, &usdc, &blnd); + + // mint lp tokens + blnd_client.mint(&samwise, &500_001_0000000); + blnd_client.approve(&samwise, &lp_token, &i128::MAX, &99999); + usdc_client.mint(&samwise, &12_501_0000000); + usdc_client.approve(&samwise, &lp_token, &i128::MAX, &99999); + lp_token_client.join_pool( + &50_000_0000000, + &vec![&e, 500_001_0000000, 12_501_0000000], + &samwise, + ); + backstop_client.deposit(&samwise, &pool_id, &50_000_0000000); + backstop_client.update_tkn_val(); + + let pool_config = PoolConfig { + oracle: oracle_id, + bstop_rate: 0, + status: 0, + }; + e.as_contract(&pool_id, || { + storage::set_admin(&e, &bombadil); + storage::set_pool_config(&e, &pool_config); + let status = execute_update_pool_status(&e); let new_pool_config = storage::get_pool_config(&e); @@ -267,7 +589,7 @@ mod tests { let pool_config = PoolConfig { oracle: oracle_id, bstop_rate: 0, - status: 0, + status: 1, }; e.as_contract(&pool_id, || { storage::set_admin(&e, &bombadil); @@ -277,12 +599,12 @@ mod tests { let new_pool_config = storage::get_pool_config(&e); assert_eq!(new_pool_config.status, status); - assert_eq!(status, 1); + assert_eq!(status, 3); }); } #[test] - fn test_update_pool_status_on_ice_q4w() { + fn test_update_pool_status_on_ice_30_q4w() { let e = Env::default(); e.budget().reset_unlimited(); e.mock_all_auths_allowing_non_root_auth(); @@ -310,7 +632,55 @@ mod tests { ); backstop_client.deposit(&samwise, &pool_id, &50_000_0000000); backstop_client.update_tkn_val(); - backstop_client.queue_withdrawal(&samwise, &pool_id, &12_500_0000000); + backstop_client.queue_withdrawal(&samwise, &pool_id, &15_000_0000000); + + let pool_config = PoolConfig { + oracle: oracle_id, + bstop_rate: 0, + status: 1, + }; + e.as_contract(&pool_id, || { + storage::set_admin(&e, &bombadil); + storage::set_pool_config(&e, &pool_config); + + let status = execute_update_pool_status(&e); + + let new_pool_config = storage::get_pool_config(&e); + assert_eq!(new_pool_config.status, status); + assert_eq!(status, 3); + }); + } + + #[test] + fn test_update_pool_status_on_ice_30_q4w_admin_active() { + let e = Env::default(); + e.budget().reset_unlimited(); + e.mock_all_auths_allowing_non_root_auth(); + let pool_id = create_pool(&e); + let oracle_id = Address::generate(&e); + + let bombadil = Address::generate(&e); + let samwise = Address::generate(&e); + + let (blnd, blnd_client) = create_token_contract(&e, &bombadil); + let (usdc, usdc_client) = create_token_contract(&e, &bombadil); + let (lp_token, lp_token_client) = create_comet_lp_pool(&e, &bombadil, &blnd, &usdc); + let (backstop_id, backstop_client) = create_backstop(&e); + setup_backstop(&e, &pool_id, &backstop_id, &lp_token, &usdc, &blnd); + + // mint lp tokens + blnd_client.mint(&samwise, &500_001_0000000); + blnd_client.approve(&samwise, &lp_token, &i128::MAX, &99999); + usdc_client.mint(&samwise, &12_501_0000000); + usdc_client.approve(&samwise, &lp_token, &i128::MAX, &99999); + lp_token_client.join_pool( + &50_000_0000000, + &vec![&e, 500_001_0000000, 12_501_0000000], + &samwise, + ); + backstop_client.deposit(&samwise, &pool_id, &50_000_0000000); + backstop_client.update_tkn_val(); + backstop_client.queue_withdrawal(&samwise, &pool_id, &15_000_0000000); let pool_config = PoolConfig { oracle: oracle_id, @@ -325,12 +695,12 @@ mod tests { let new_pool_config = storage::get_pool_config(&e); assert_eq!(new_pool_config.status, status); - assert_eq!(status, 1); + assert_eq!(status, 0); }); } #[test] - fn test_update_pool_status_frozen() { + fn test_update_pool_status_on_ice_50_q4w_admin_active() { let e = Env::default(); e.budget().reset_unlimited(); e.mock_all_auths_allowing_non_root_auth(); @@ -371,6 +741,101 @@ mod tests { let status = execute_update_pool_status(&e); + let new_pool_config = storage::get_pool_config(&e); + assert_eq!(new_pool_config.status, status); + assert_eq!(status, 3); + }); + } + + #[test] + fn test_update_pool_status_frozen() { + let e = Env::default(); + e.budget().reset_unlimited(); + e.mock_all_auths_allowing_non_root_auth(); + let pool_id = create_pool(&e); + let oracle_id = Address::generate(&e); + + let bombadil = Address::generate(&e); + let samwise = Address::generate(&e); + + let (blnd, blnd_client) = create_token_contract(&e, &bombadil); + let (usdc, usdc_client) = create_token_contract(&e, &bombadil); + let (lp_token, lp_token_client) = create_comet_lp_pool(&e, &bombadil, &blnd, &usdc); + let (backstop_id, backstop_client) = create_backstop(&e); + setup_backstop(&e, &pool_id, &backstop_id, &lp_token, &usdc, &blnd); + + // mint lp tokens + blnd_client.mint(&samwise, &500_001_0000000); + blnd_client.approve(&samwise, &lp_token, &i128::MAX, &99999); + usdc_client.mint(&samwise, &12_501_0000000); + usdc_client.approve(&samwise, &lp_token, &i128::MAX, &99999); + lp_token_client.join_pool( + &50_000_0000000, + &vec![&e, 500_001_0000000, 12_501_0000000], + &samwise, + ); + backstop_client.deposit(&samwise, &pool_id, &50_000_0000000); + backstop_client.update_tkn_val(); + backstop_client.queue_withdrawal(&samwise, &pool_id, &30_000_0000000); + + let pool_config = PoolConfig { + oracle: oracle_id, + bstop_rate: 0, + status: 1, + }; + e.as_contract(&pool_id, || { + storage::set_admin(&e, &bombadil); + storage::set_pool_config(&e, &pool_config); + + let status = execute_update_pool_status(&e); + + let new_pool_config = storage::get_pool_config(&e); + assert_eq!(new_pool_config.status, status); + assert_eq!(status, 5); + }); + } + #[test] + fn test_update_pool_status_frozen_admin_on_ice() { + let e = Env::default(); + e.budget().reset_unlimited(); + e.mock_all_auths_allowing_non_root_auth(); + let pool_id = create_pool(&e); + let oracle_id = Address::generate(&e); + + let bombadil = Address::generate(&e); + let samwise = Address::generate(&e); + + let (blnd, blnd_client) = create_token_contract(&e, &bombadil); + let (usdc, usdc_client) = create_token_contract(&e, &bombadil); + let (lp_token, lp_token_client) = create_comet_lp_pool(&e, &bombadil, &blnd, &usdc); + let (backstop_id, backstop_client) = create_backstop(&e); + setup_backstop(&e, &pool_id, &backstop_id, &lp_token, &usdc, &blnd); + + // mint lp tokens + blnd_client.mint(&samwise, &500_001_0000000); + blnd_client.approve(&samwise, &lp_token, &i128::MAX, &99999); + usdc_client.mint(&samwise, &12_501_0000000); + usdc_client.approve(&samwise, &lp_token, &i128::MAX, &99999); + lp_token_client.join_pool( + &50_000_0000000, + &vec![&e, 500_001_0000000, 12_501_0000000], + &samwise, + ); + backstop_client.deposit(&samwise, &pool_id, &50_000_0000000); + backstop_client.update_tkn_val(); + backstop_client.queue_withdrawal(&samwise, &pool_id, &30_000_0000000); + + let pool_config = PoolConfig { + oracle: oracle_id, + bstop_rate: 0, + status: 2, + }; + e.as_contract(&pool_id, || { + storage::set_admin(&e, &bombadil); + storage::set_pool_config(&e, &pool_config); + + let status = execute_update_pool_status(&e); + let new_pool_config = storage::get_pool_config(&e); assert_eq!(new_pool_config.status, status); assert_eq!(status, 2); @@ -378,8 +843,7 @@ mod tests { } #[test] - #[should_panic(expected = "Error(Contract, #11)")] - fn test_update_pool_status_admin_frozen() { + fn test_update_pool_status_frozen_75_q4w() { let e = Env::default(); e.budget().reset_unlimited(); e.mock_all_auths_allowing_non_root_auth(); @@ -407,11 +871,60 @@ mod tests { ); backstop_client.deposit(&samwise, &pool_id, &50_000_0000000); backstop_client.update_tkn_val(); + backstop_client.queue_withdrawal(&samwise, &pool_id, &40_000_0000000); let pool_config = PoolConfig { oracle: oracle_id, bstop_rate: 0, - status: 3, + status: 2, + }; + e.as_contract(&pool_id, || { + storage::set_admin(&e, &bombadil); + storage::set_pool_config(&e, &pool_config); + + let status = execute_update_pool_status(&e); + + let new_pool_config = storage::get_pool_config(&e); + assert_eq!(new_pool_config.status, status); + assert_eq!(status, 5); + }); + } + + #[test] + #[should_panic(expected = "Error(Auth, InvalidAction)")] + fn test_update_pool_status_admin_frozen() { + let e = Env::default(); + e.budget().reset_unlimited(); + // e.mock_all_auths_allowing_non_root_auth(); + let pool_id = create_pool(&e); + let oracle_id = Address::generate(&e); + + let bombadil = Address::generate(&e); + let samwise = Address::generate(&e); + + let (blnd, blnd_client) = create_token_contract(&e, &bombadil); + let (usdc, usdc_client) = create_token_contract(&e, &bombadil); + let (lp_token, lp_token_client) = create_comet_lp_pool(&e, &bombadil, &blnd, &usdc); + let (backstop_id, backstop_client) = create_backstop(&e); + setup_backstop(&e, &pool_id, &backstop_id, &lp_token, &usdc, &blnd); + + // mint lp tokens + blnd_client.mint(&samwise, &500_001_0000000); + blnd_client.approve(&samwise, &lp_token, &i128::MAX, &99999); + usdc_client.mint(&samwise, &12_501_0000000); + usdc_client.approve(&samwise, &lp_token, &i128::MAX, &99999); + lp_token_client.join_pool( + &50_000_0000000, + &vec![&e, 500_001_0000000, 12_501_0000000], + &samwise, + ); + backstop_client.deposit(&samwise, &pool_id, &50_000_0000000); + backstop_client.update_tkn_val(); + + let pool_config = PoolConfig { + oracle: oracle_id, + bstop_rate: 0, + status: 4, }; e.as_contract(&pool_id, || { storage::set_admin(&e, &bombadil); @@ -421,6 +934,54 @@ mod tests { }); } + #[test] + fn test_admin_update_pool_status_unfreeze() { + let e = Env::default(); + e.budget().reset_unlimited(); + e.mock_all_auths_allowing_non_root_auth(); + + let pool_id = create_pool(&e); + let oracle_id = Address::generate(&e); + + let bombadil = Address::generate(&e); + let samwise = Address::generate(&e); + + let (blnd, blnd_client) = create_token_contract(&e, &bombadil); + let (usdc, usdc_client) = create_token_contract(&e, &bombadil); + let (lp_token, lp_token_client) = create_comet_lp_pool(&e, &bombadil, &blnd, &usdc); + let (backstop_id, backstop_client) = create_backstop(&e); + setup_backstop(&e, &pool_id, &backstop_id, &lp_token, &usdc, &blnd); + + // mint lp tokens + blnd_client.mint(&samwise, &500_001_0000000); + blnd_client.approve(&samwise, &lp_token, &i128::MAX, &99999); + usdc_client.mint(&samwise, &12_501_0000000); + usdc_client.approve(&samwise, &lp_token, &i128::MAX, &99999); + lp_token_client.join_pool( + &50_000_0000000, + &vec![&e, 500_001_0000000, 12_501_0000000], + &samwise, + ); + backstop_client.deposit(&samwise, &pool_id, &50_000_0000000); + backstop_client.update_tkn_val(); + backstop_client.queue_withdrawal(&samwise, &pool_id, &12_500_0000000); + + let pool_config = PoolConfig { + oracle: oracle_id, + bstop_rate: 0, + status: 5, + }; + e.as_contract(&pool_id, || { + storage::set_admin(&e, &bombadil); + storage::set_pool_config(&e, &pool_config); + + execute_set_pool_status(&e, 0); + + let new_pool_config = storage::get_pool_config(&e); + assert_eq!(new_pool_config.status, 0); + }); + } + #[test] fn test_calc_pool_backstop_threshold() { let e = Env::default(); diff --git a/test-suites/tests/test_pool.rs b/test-suites/tests/test_pool.rs index 3fe00764..98a9b382 100644 --- a/test-suites/tests/test_pool.rs +++ b/test-suites/tests/test_pool.rs @@ -717,7 +717,7 @@ fn test_pool_config() { ); // Set status (admin only) - pool_fixture.pool.set_status(&1); + pool_fixture.pool.set_status(&2); assert_eq!( fixture.env.auths()[0], ( @@ -726,14 +726,14 @@ fn test_pool_config() { function: AuthorizedFunction::Contract(( pool_fixture.pool.address.clone(), Symbol::new(&fixture.env, "set_status"), - vec![&fixture.env, 1u32.into_val(&fixture.env)] + vec![&fixture.env, 2u32.into_val(&fixture.env)] )), sub_invocations: std::vec![] } ) ); let new_pool_config = fixture.read_pool_config(0); - assert_eq!(new_pool_config.status, 1); + assert_eq!(new_pool_config.status, 2); let event = vec![&fixture.env, fixture.env.events().all().last_unchecked()]; assert_eq!( event, @@ -742,16 +742,78 @@ fn test_pool_config() { ( pool_fixture.pool.address.clone(), (Symbol::new(&fixture.env, "set_status"), new_admin.clone()).into_val(&fixture.env), - 1u32.into_val(&fixture.env) + 2u32.into_val(&fixture.env) + ) + ] + ); + //revert to standard status (admin only) + pool_fixture.pool.set_status(&0); + assert_eq!( + fixture.env.auths()[0], + ( + new_admin.clone(), + AuthorizedInvocation { + function: AuthorizedFunction::Contract(( + pool_fixture.pool.address.clone(), + Symbol::new(&fixture.env, "set_status"), + vec![&fixture.env, 0u32.into_val(&fixture.env)] + )), + sub_invocations: std::vec![] + } + ) + ); + let new_pool_config = fixture.read_pool_config(0); + assert_eq!(new_pool_config.status, 0); + let event = vec![&fixture.env, fixture.env.events().all().last_unchecked()]; + assert_eq!( + event, + vec![ + &fixture.env, + ( + pool_fixture.pool.address.clone(), + (Symbol::new(&fixture.env, "set_status"), new_admin.clone()).into_val(&fixture.env), + 0u32.into_val(&fixture.env) + ) + ] + ); + + // Queue 50% of backstop for withdrawal + fixture.backstop.queue_withdrawal( + &fixture.users[0], + &pool_fixture.pool.address, + &(25_000 * SCALAR_7), + ); + + // Update status (backstop is unhealthy, so this should update to backstop on-ice) + pool_fixture.pool.update_status(); + assert_eq!(fixture.env.auths().len(), 0); + let new_pool_config = fixture.read_pool_config(0); + assert_eq!(new_pool_config.status, 3); + let event = vec![&fixture.env, fixture.env.events().all().last_unchecked()]; + assert_eq!( + event, + vec![ + &fixture.env, + ( + pool_fixture.pool.address.clone(), + (Symbol::new(&fixture.env, "set_status"),).into_val(&fixture.env), + 3u32.into_val(&fixture.env) ) ] ); + // Dequeue 50% of backstop for withdrawal + fixture.backstop.dequeue_withdrawal( + &fixture.users[0], + &pool_fixture.pool.address, + &(25_000 * SCALAR_7), + ); + // Update status (backstop is healthy, so this should update to active) pool_fixture.pool.update_status(); assert_eq!(fixture.env.auths().len(), 0); let new_pool_config = fixture.read_pool_config(0); - assert_eq!(new_pool_config.status, 0); + assert_eq!(new_pool_config.status, 1); let event = vec![&fixture.env, fixture.env.events().all().last_unchecked()]; assert_eq!( event, @@ -760,7 +822,7 @@ fn test_pool_config() { ( pool_fixture.pool.address.clone(), (Symbol::new(&fixture.env, "set_status"),).into_val(&fixture.env), - 0u32.into_val(&fixture.env) + 1u32.into_val(&fixture.env) ) ] );