From 4d7ed64e664876f2316f58dc99c8591964785056 Mon Sep 17 00:00:00 2001 From: markuspluna <59978114+markuspluna@users.noreply.github.com> Date: Wed, 29 Nov 2023 19:04:11 -0500 Subject: [PATCH 1/5] issue #150 admin status expansion --- pool/src/contract.rs | 10 ++------ pool/src/pool/status.rs | 57 +++++++++++++++++------------------------ 2 files changed, 25 insertions(+), 42 deletions(-) diff --git a/pool/src/contract.rs b/pool/src/contract.rs index 25b8cbc5..2e5cbd77 100644 --- a/pool/src/contract.rs +++ b/pool/src/contract.rs @@ -313,7 +313,7 @@ impl Pool for PoolContract { fn update_status(e: Env) -> u32 { storage::bump_instance(&e); - let new_status = pool::execute_update_pool_status(&e); + let new_status = pool::execute_update_pool_status(&e, 11); //status input is not used here e.events() .publish((Symbol::new(&e, "set_status"),), new_status); @@ -322,13 +322,7 @@ impl Pool for PoolContract { fn set_status(e: Env, pool_status: u32) { storage::bump_instance(&e); - let admin = storage::get_admin(&e); - admin.require_auth(); - - pool::set_pool_status(&e, pool_status); - - e.events() - .publish((Symbol::new(&e, "set_status"), admin), pool_status); + pool::execute_update_pool_status(&e, pool_status); } /********* Emission Functions **********/ diff --git a/pool/src/pool/status.rs b/pool/src/pool/status.rs index 5cf14068..cea79c1b 100644 --- a/pool/src/pool/status.rs +++ b/pool/src/pool/status.rs @@ -1,59 +1,48 @@ use crate::{ constants::SCALAR_7, dependencies::{BackstopClient, PoolBackstopData}, - errors::PoolError, storage, }; -use soroban_sdk::{panic_with_error, Env}; +use soroban_sdk::{Env, Symbol}; /// Update the pool status based on the backstop module #[allow(clippy::zero_prefixed_literal)] #[allow(clippy::inconsistent_digit_grouping)] -pub fn execute_update_pool_status(e: &Env) -> u32 { +pub fn execute_update_pool_status(e: &Env, pool_status: u32) -> 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; + let threshold = calc_pool_backstop_threshold(&pool_backstop_data); + let mut met_threshold = true; + if threshold < SCALAR_7 { + met_threshold = false; + } + // we only care about admin status' if the pool has met the threshold - otherwise default takes over + if (pool_config.status % 2 == 0 || pool_status % 2 == 0) && met_threshold { + let admin = storage::get_admin(&e); + // admin auth required to set or change admin status' + admin.require_auth(); + pool_config.status = pool_status; + e.events() + .publish((Symbol::new(&e, "set_status"), admin), pool_status); } else { - pool_config.status = 0; + if pool_backstop_data.q4w_pct >= 0_5000000 { + pool_config.status = 5; + } else if pool_backstop_data.q4w_pct >= 0_2500000 || !met_threshold { + pool_config.status = 3; + } else { + pool_config.status = 1; + } } storage::set_pool_config(e, &pool_config); pool_config.status } -/// Update the pool status -#[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); - } - } - - let mut pool_config = storage::get_pool_config(e); - pool_config.status = pool_status; - storage::set_pool_config(e, &pool_config); -} - /// Calculate the threshold for the pool's backstop balance /// /// Returns the threshold as a percentage^5 in SCALAR_7 points such that SCALAR_7 = 100% From 52fed8dea03a407ca1d3d9feee83f248d622907b Mon Sep 17 00:00:00 2001 From: markuspluna <59978114+markuspluna@users.noreply.github.com> Date: Wed, 29 Nov 2023 19:09:23 -0500 Subject: [PATCH 2/5] update action checks and require liq cancels to have an active pool --- pool/src/pool/mod.rs | 2 +- pool/src/pool/pool.rs | 6 +++--- pool/src/pool/status.rs | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pool/src/pool/mod.rs b/pool/src/pool/mod.rs index 44f8c3e6..4170644c 100644 --- a/pool/src/pool/mod.rs +++ b/pool/src/pool/mod.rs @@ -30,4 +30,4 @@ 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_update_pool_status}; diff --git a/pool/src/pool/pool.rs b/pool/src/pool/pool.rs index 230dbba9..007242bf 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); } diff --git a/pool/src/pool/status.rs b/pool/src/pool/status.rs index cea79c1b..35376860 100644 --- a/pool/src/pool/status.rs +++ b/pool/src/pool/status.rs @@ -125,7 +125,7 @@ mod tests { storage::set_admin(&e, &bombadil); storage::set_pool_config(&e, &pool_config); - set_pool_status(&e, 0); + execute_update_pool_status(&e, 0); let new_pool_config = storage::get_pool_config(&e); assert_eq!(new_pool_config.status, 0); @@ -215,7 +215,7 @@ mod tests { storage::set_admin(&e, &bombadil); storage::set_pool_config(&e, &pool_config); - let status = execute_update_pool_status(&e); + let status = execute_update_pool_status(&e, 11); let new_pool_config = storage::get_pool_config(&e); assert_eq!(new_pool_config.status, status); @@ -262,7 +262,7 @@ mod tests { storage::set_admin(&e, &bombadil); storage::set_pool_config(&e, &pool_config); - let status = execute_update_pool_status(&e); + let status = execute_update_pool_status(&e, 11); let new_pool_config = storage::get_pool_config(&e); assert_eq!(new_pool_config.status, status); @@ -310,7 +310,7 @@ mod tests { storage::set_admin(&e, &bombadil); storage::set_pool_config(&e, &pool_config); - let status = execute_update_pool_status(&e); + let status = execute_update_pool_status(&e, 11); let new_pool_config = storage::get_pool_config(&e); assert_eq!(new_pool_config.status, status); @@ -358,7 +358,7 @@ mod tests { storage::set_admin(&e, &bombadil); storage::set_pool_config(&e, &pool_config); - let status = execute_update_pool_status(&e); + let status = execute_update_pool_status(&e, 11); let new_pool_config = storage::get_pool_config(&e); assert_eq!(new_pool_config.status, status); @@ -406,7 +406,7 @@ mod tests { storage::set_admin(&e, &bombadil); storage::set_pool_config(&e, &pool_config); - execute_update_pool_status(&e); + execute_update_pool_status(&e, 11); }); } From c2e585baa05a23a16f8510a11bb7170007988264 Mon Sep 17 00:00:00 2001 From: markuspluna <59978114+markuspluna@users.noreply.github.com> Date: Thu, 30 Nov 2023 11:00:16 -0500 Subject: [PATCH 3/5] updated tests --- pool/src/pool/pool.rs | 45 ++++++++++++- pool/src/pool/status.rs | 114 ++++++++++++++++++++++++++++++--- test-suites/tests/test_pool.rs | 42 ++++++++++-- 3 files changed, 183 insertions(+), 18 deletions(-) diff --git a/pool/src/pool/pool.rs b/pool/src/pool/pool.rs index 007242bf..e1e5e729 100644 --- a/pool/src/pool/pool.rs +++ b/pool/src/pool/pool.rs @@ -267,7 +267,7 @@ mod tests { let pool_config = PoolConfig { oracle, bstop_rate: 0_200_000_000, - status: 1, + status: 3, }; e.as_contract(&pool, || { storage::set_pool_config(&e, &pool_config); @@ -296,6 +296,45 @@ mod tests { }); } + #[test] + #[should_panic(expected = "Error(Contract, #11)")] + fn test_require_action_allowed_cancel_liquidation_while_on_ice_panics() { + let e = Env::default(); + + let pool = testutils::create_pool(&e); + let oracle = Address::random(&e); + let pool_config = PoolConfig { + oracle, + bstop_rate: 0_200_000_000, + status: 3, + }; + e.as_contract(&pool, || { + 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::random(&e); + let pool_config = PoolConfig { + oracle, + bstop_rate: 0_200_000_000, + status: 0, + }; + 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() { @@ -306,7 +345,7 @@ mod tests { let pool_config = PoolConfig { oracle, bstop_rate: 0_200_000_000, - status: 2, + status: 5, }; e.as_contract(&pool, || { storage::set_pool_config(&e, &pool_config); @@ -326,7 +365,7 @@ mod tests { let pool_config = PoolConfig { oracle, bstop_rate: 0_200_000_000, - status: 2, + status: 5, }; 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 35376860..cfc5e447 100644 --- a/pool/src/pool/status.rs +++ b/pool/src/pool/status.rs @@ -219,7 +219,7 @@ mod tests { let new_pool_config = storage::get_pool_config(&e); assert_eq!(new_pool_config.status, status); - assert_eq!(status, 0); + assert_eq!(status, 1); }); } @@ -266,7 +266,7 @@ 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); }); } @@ -304,7 +304,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); @@ -314,7 +314,7 @@ 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); }); } @@ -352,7 +352,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); @@ -362,16 +362,16 @@ mod tests { let new_pool_config = storage::get_pool_config(&e); assert_eq!(new_pool_config.status, status); - assert_eq!(status, 2); + assert_eq!(status, 5); }); } #[test] - #[should_panic(expected = "Error(Contract, #11)")] + #[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(); + // e.mock_all_auths_allowing_non_root_auth(); let pool_id = create_pool(&e); let oracle_id = Address::random(&e); @@ -400,7 +400,7 @@ mod tests { let pool_config = PoolConfig { oracle: oracle_id, bstop_rate: 0, - status: 3, + status: 4, }; e.as_contract(&pool_id, || { storage::set_admin(&e, &bombadil); @@ -410,6 +410,102 @@ 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::random(&e); + + let bombadil = Address::random(&e); + let samwise = Address::random(&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, &25_000_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); + + let status = execute_update_pool_status(&e, 0); + + let new_pool_config = storage::get_pool_config(&e); + assert_eq!(new_pool_config.status, status); + assert_eq!(status, 0); + }); + } + + #[test] + fn test_admin_update_pool_status_freeze() { + 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::random(&e); + + let bombadil = Address::random(&e); + let samwise = Address::random(&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); + + let status = execute_update_pool_status(&e, 4); + + let new_pool_config = storage::get_pool_config(&e); + assert_eq!(new_pool_config.status, status); + assert_eq!(status, 4); + }); + } + #[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 4a3ec7d2..39d97065 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,7 +742,37 @@ 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(&3); + 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, 3u32.into_val(&fixture.env)] + )), + sub_invocations: std::vec![] + } + ) + ); + 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"), new_admin.clone()).into_val(&fixture.env), + 3u32.into_val(&fixture.env) ) ] ); @@ -751,7 +781,7 @@ fn test_pool_config() { 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 +790,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) ) ] ); From 9de293c6a8538a82f773f15ae8c399e888af43d6 Mon Sep 17 00:00:00 2001 From: markuspluna <59978114+markuspluna@users.noreply.github.com> Date: Tue, 12 Dec 2023 18:04:01 -0800 Subject: [PATCH 4/5] Issue #150 status mgmt updates --- pool-factory/src/test.rs | 2 +- pool/src/contract.rs | 31 ++- pool/src/pool/config.rs | 4 +- pool/src/pool/pool.rs | 4 +- pool/src/pool/status.rs | 496 ++++++++++++++++++++++++++++++--- test-suites/tests/test_pool.rs | 38 ++- 6 files changed, 515 insertions(+), 60 deletions(-) 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 f6924712..6c77851f 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 **********/ @@ -302,7 +309,7 @@ impl Pool for PoolContract { } fn update_status(e: Env) -> u32 { - storage::bump_instance(&e); + storage::extend_instance(&e); let new_status = pool::execute_update_pool_status(&e, 11); //status input is not used here e.events() @@ -311,7 +318,7 @@ impl Pool for PoolContract { } fn set_status(e: Env, pool_status: u32) { - storage::bump_instance(&e); + storage::extend_instance(&e); pool::execute_update_pool_status(&e, pool_status); } 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/pool.rs b/pool/src/pool/pool.rs index d29e47fd..b621585a 100644 --- a/pool/src/pool/pool.rs +++ b/pool/src/pool/pool.rs @@ -302,7 +302,7 @@ mod tests { let e = Env::default(); let pool = testutils::create_pool(&e); - let oracle = Address::random(&e); + let oracle = Address::generate(&e); let pool_config = PoolConfig { oracle, bstop_rate: 0_200_000_000, @@ -321,7 +321,7 @@ mod tests { let e = Env::default(); let pool = testutils::create_pool(&e); - let oracle = Address::random(&e); + let oracle = Address::generate(&e); let pool_config = PoolConfig { oracle, bstop_rate: 0_200_000_000, diff --git a/pool/src/pool/status.rs b/pool/src/pool/status.rs index 51ab8682..3541c76c 100644 --- a/pool/src/pool/status.rs +++ b/pool/src/pool/status.rs @@ -1,11 +1,12 @@ use crate::{ constants::SCALAR_7, dependencies::{BackstopClient, PoolBackstopData}, - storage, + storage, PoolError, }; -use soroban_sdk::{Env, Symbol}; +use soroban_sdk::{panic_with_error, Env, Symbol}; /// Update the pool status based on the backstop module +/// #[allow(clippy::zero_prefixed_literal)] #[allow(clippy::inconsistent_digit_grouping)] pub fn execute_update_pool_status(e: &Env, pool_status: u32) -> u32 { @@ -21,20 +22,64 @@ pub fn execute_update_pool_status(e: &Env, pool_status: u32) -> u32 { if threshold < SCALAR_7 { met_threshold = false; } - // we only care about admin status' if the pool has met the threshold - otherwise default takes over - if (pool_config.status % 2 == 0 || pool_status % 2 == 0) && met_threshold { + // even numbered statuses are admin controlled + if pool_status % 2 == 0 { let admin = storage::get_admin(&e); - // admin auth required to set or change admin status' + // admin auth required to set admin status' admin.require_auth(); - pool_config.status = pool_status; + match pool_status { + 0 => { + // Threshold must be met and q4w must be under 50% for the admin to set Active + if !met_threshold || pool_backstop_data.q4w_pct >= 0_5000000 { + panic_with_error!(e, PoolError::BadRequest); + } + // 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::BadRequest); + } + // 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); + } + } e.events() .publish((Symbol::new(&e, "set_status"), admin), pool_status); } else { - if pool_backstop_data.q4w_pct >= 0_5000000 { + // Admin Frozen supersedes all other statuses + if pool_config.status == 4 { + panic_with_error!(e, PoolError::BadRequest); + // Q4W over 75% freezes the pool + // Q4W over 60% freezes the pool if it is not Admin On-Ice + } else if pool_backstop_data.q4w_pct >= 0_7500000 + || (pool_backstop_data.q4w_pct >= 0_6000000 && pool_config.status != 2) + { + // Frozen pool_config.status = 5; - } else if pool_backstop_data.q4w_pct >= 0_2500000 || !met_threshold { + // Admin On-Ice supersedes backstop triggered on-ice + // Q4w over 50% puts the pool on-ice + // Threshold not being met puts the pool on-ice + // As long as pool isn't admin active q4w over 30% puts pool on-ice + } else if pool_config.status != 2 + && (pool_backstop_data.q4w_pct >= 0_5000000 + || !met_threshold + || (pool_config.status != 0 && pool_backstop_data.q4w_pct >= 0_3000000)) + { + // On-Ice pool_config.status = 3; - } else { + // Admin status' all supersede backstop triggered active + } else if pool_config.status % 2 != 0 { + // Active pool_config.status = 1; } } @@ -87,7 +132,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(); @@ -133,8 +178,8 @@ mod tests { } #[test] - #[should_panic(expected = "Error(Contract, #11)")] - fn test_set_pool_status_blocks_without_backstop_minimum() { + #[should_panic(expected = "Error(Contract, #2)")] + 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(); @@ -172,12 +217,146 @@ mod tests { storage::set_admin(&e, &bombadil); storage::set_pool_config(&e, &pool_config); - set_pool_status(&e, 0); + execute_update_pool_status(&e, 0); }); } #[test] - fn test_update_pool_status_active() { + #[should_panic(expected = "Error(Contract, #2)")] + 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.withdraw(&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_update_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_update_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, #2)")] + 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.withdraw(&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_update_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(); @@ -215,6 +394,52 @@ mod tests { storage::set_admin(&e, &bombadil); storage::set_pool_config(&e, &pool_config); + execute_update_pool_status(&e, 4); + + let new_pool_config = storage::get_pool_config(&e); + assert_eq!(new_pool_config.status, 4); + }); + } + + #[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, 11); let new_pool_config = storage::get_pool_config(&e); @@ -223,6 +448,53 @@ mod tests { }); } + #[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, 11); + + let new_pool_config = storage::get_pool_config(&e); + assert_eq!(new_pool_config.status, status); + assert_eq!(status, 0); + }); + } + #[test] fn test_update_pool_status_on_ice_tokens() { let e = Env::default(); @@ -256,7 +528,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); @@ -271,7 +543,7 @@ mod tests { } #[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(); @@ -299,7 +571,7 @@ 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, @@ -319,7 +591,55 @@ mod tests { } #[test] - fn test_update_pool_status_frozen() { + 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, + 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, 11); + + let new_pool_config = storage::get_pool_config(&e); + assert_eq!(new_pool_config.status, status); + assert_eq!(status, 0); + }); + } + + #[test] + 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(); @@ -349,6 +669,54 @@ mod tests { backstop_client.update_tkn_val(); backstop_client.queue_withdrawal(&samwise, &pool_id, &25_000_0000000); + 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, 11); + + 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, @@ -365,18 +733,16 @@ mod tests { assert_eq!(status, 5); }); } - #[test] - #[should_panic(expected = "Error(Auth, InvalidAction)")] - fn test_update_pool_status_admin_frozen() { + 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(); + e.mock_all_auths_allowing_non_root_auth(); let pool_id = create_pool(&e); - let oracle_id = Address::random(&e); + let oracle_id = Address::generate(&e); - let bombadil = Address::random(&e); - let samwise = Address::random(&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); @@ -396,31 +762,35 @@ mod tests { ); 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: 4, + status: 2, }; e.as_contract(&pool_id, || { storage::set_admin(&e, &bombadil); storage::set_pool_config(&e, &pool_config); - execute_update_pool_status(&e, 11); + let status = execute_update_pool_status(&e, 11); + + let new_pool_config = storage::get_pool_config(&e); + assert_eq!(new_pool_config.status, status); + assert_eq!(status, 2); }); } #[test] - fn test_admin_update_pool_status_unfreeze() { + fn test_update_pool_status_frozen_75_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::random(&e); + let oracle_id = Address::generate(&e); - let bombadil = Address::random(&e); - let samwise = Address::random(&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); @@ -440,30 +810,75 @@ mod tests { ); backstop_client.deposit(&samwise, &pool_id, &50_000_0000000); backstop_client.update_tkn_val(); - backstop_client.queue_withdrawal(&samwise, &pool_id, &25_000_0000000); + backstop_client.queue_withdrawal(&samwise, &pool_id, &40_000_0000000); let pool_config = PoolConfig { oracle: oracle_id, bstop_rate: 0, - status: 5, + 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, 0); + let status = execute_update_pool_status(&e, 11); let new_pool_config = storage::get_pool_config(&e); assert_eq!(new_pool_config.status, status); - assert_eq!(status, 0); + assert_eq!(status, 5); }); } #[test] - fn test_admin_update_pool_status_freeze() { + #[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); + storage::set_pool_config(&e, &pool_config); + + execute_update_pool_status(&e, 11); + }); + } + + #[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); @@ -488,21 +903,22 @@ 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); let pool_config = PoolConfig { oracle: oracle_id, bstop_rate: 0, - status: 1, + status: 5, }; e.as_contract(&pool_id, || { storage::set_admin(&e, &bombadil); storage::set_pool_config(&e, &pool_config); - let status = execute_update_pool_status(&e, 4); + let status = execute_update_pool_status(&e, 0); let new_pool_config = storage::get_pool_config(&e); assert_eq!(new_pool_config.status, status); - assert_eq!(status, 4); + assert_eq!(status, 0); }); } diff --git a/test-suites/tests/test_pool.rs b/test-suites/tests/test_pool.rs index 1dd61f21..98a9b382 100644 --- a/test-suites/tests/test_pool.rs +++ b/test-suites/tests/test_pool.rs @@ -747,7 +747,7 @@ fn test_pool_config() { ] ); //revert to standard status (admin only) - pool_fixture.pool.set_status(&3); + pool_fixture.pool.set_status(&0); assert_eq!( fixture.env.auths()[0], ( @@ -756,14 +756,14 @@ fn test_pool_config() { function: AuthorizedFunction::Contract(( pool_fixture.pool.address.clone(), Symbol::new(&fixture.env, "set_status"), - vec![&fixture.env, 3u32.into_val(&fixture.env)] + 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, 3); + assert_eq!(new_pool_config.status, 0); let event = vec![&fixture.env, fixture.env.events().all().last_unchecked()]; assert_eq!( event, @@ -772,11 +772,43 @@ fn test_pool_config() { ( 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); From 6556498dbeebbd22a39831750db04c349b952909 Mon Sep 17 00:00:00 2001 From: markuspluna <59978114+markuspluna@users.noreply.github.com> Date: Thu, 14 Dec 2023 16:19:33 -0800 Subject: [PATCH 5/5] Pool: cleaned status code --- pool/src/contract.rs | 8 +- pool/src/errors.rs | 1 + pool/src/pool/mod.rs | 4 +- pool/src/pool/pool.rs | 14 +-- pool/src/pool/status.rs | 224 +++++++++++++++++++++++++--------------- 5 files changed, 159 insertions(+), 92 deletions(-) diff --git a/pool/src/contract.rs b/pool/src/contract.rs index 6c77851f..eb7f1dfc 100644 --- a/pool/src/contract.rs +++ b/pool/src/contract.rs @@ -310,7 +310,7 @@ impl Pool for PoolContract { fn update_status(e: Env) -> u32 { storage::extend_instance(&e); - let new_status = pool::execute_update_pool_status(&e, 11); //status input is not used here + let new_status = pool::execute_update_pool_status(&e); e.events() .publish((Symbol::new(&e, "set_status"),), new_status); @@ -319,7 +319,11 @@ impl Pool for PoolContract { fn set_status(e: Env, pool_status: u32) { storage::extend_instance(&e); - pool::execute_update_pool_status(&e, pool_status); + let admin = storage::get_admin(&e); + admin.require_auth(); + pool::execute_set_pool_status(&e, pool_status); + e.events() + .publish((Symbol::new(&e, "set_status"), admin), pool_status); } /********* Emission Functions **********/ 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/mod.rs b/pool/src/pool/mod.rs index d501510a..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}; +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 b621585a..f1cef37d 100644 --- a/pool/src/pool/pool.rs +++ b/pool/src/pool/pool.rs @@ -267,7 +267,7 @@ mod tests { let pool_config = PoolConfig { oracle, bstop_rate: 0_200_000_000, - status: 3, + 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); @@ -306,7 +306,7 @@ mod tests { let pool_config = PoolConfig { oracle, bstop_rate: 0_200_000_000, - status: 3, + status: 2, }; e.as_contract(&pool, || { storage::set_pool_config(&e, &pool_config); @@ -325,7 +325,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); @@ -345,7 +345,7 @@ mod tests { let pool_config = PoolConfig { oracle, bstop_rate: 0_200_000_000, - status: 5, + status: 4, }; e.as_contract(&pool, || { storage::set_pool_config(&e, &pool_config); @@ -365,7 +365,7 @@ mod tests { let pool_config = PoolConfig { oracle, bstop_rate: 0_200_000_000, - status: 5, + status: 4, }; e.as_contract(&pool, || { storage::set_pool_config(&e, &pool_config); @@ -384,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 3541c76c..283d8da0 100644 --- a/pool/src/pool/status.rs +++ b/pool/src/pool/status.rs @@ -3,13 +3,12 @@ use crate::{ dependencies::{BackstopClient, PoolBackstopData}, storage, PoolError, }; -use soroban_sdk::{panic_with_error, Env, Symbol}; +use soroban_sdk::{panic_with_error, Env}; /// Update the pool status based on the backstop module -/// #[allow(clippy::zero_prefixed_literal)] #[allow(clippy::inconsistent_digit_grouping)] -pub fn execute_update_pool_status(e: &Env, pool_status: u32) -> u32 { +pub fn execute_update_pool_status(e: &Env) -> u32 { let mut pool_config = storage::get_pool_config(e); // check the pool has met minimum backstop deposits @@ -22,70 +21,89 @@ pub fn execute_update_pool_status(e: &Env, pool_status: u32) -> u32 { if threshold < SCALAR_7 { met_threshold = false; } - // even numbered statuses are admin controlled - if pool_status % 2 == 0 { - let admin = storage::get_admin(&e); - // admin auth required to set admin status' - admin.require_auth(); - match pool_status { - 0 => { - // Threshold must be met and q4w must be under 50% for the admin to set Active - if !met_threshold || pool_backstop_data.q4w_pct >= 0_5000000 { - panic_with_error!(e, PoolError::BadRequest); - } - // Admin Active - pool_config.status = 0; + + 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; } - 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::BadRequest); - } - // Admin On-Ice - pool_config.status = 2; + } + // 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; } - 4 => { - // Admin can always freeze the pool - // Admin Frozen - pool_config.status = 4; + } + } + storage::set_pool_config(e, &pool_config); + pool_config.status +} + +/// Admin set the pool status +#[allow(clippy::zero_prefixed_literal)] +#[allow(clippy::inconsistent_digit_grouping)] +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); } - _ => { - panic_with_error!(e, PoolError::BadRequest); + // 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; } - e.events() - .publish((Symbol::new(&e, "set_status"), admin), pool_status); - } else { - // Admin Frozen supersedes all other statuses - if pool_config.status == 4 { + _ => { panic_with_error!(e, PoolError::BadRequest); - // Q4W over 75% freezes the pool - // Q4W over 60% freezes the pool if it is not Admin On-Ice - } else if pool_backstop_data.q4w_pct >= 0_7500000 - || (pool_backstop_data.q4w_pct >= 0_6000000 && pool_config.status != 2) - { - // Frozen - pool_config.status = 5; - // Admin On-Ice supersedes backstop triggered on-ice - // Q4w over 50% puts the pool on-ice - // Threshold not being met puts the pool on-ice - // As long as pool isn't admin active q4w over 30% puts pool on-ice - } else if pool_config.status != 2 - && (pool_backstop_data.q4w_pct >= 0_5000000 - || !met_threshold - || (pool_config.status != 0 && pool_backstop_data.q4w_pct >= 0_3000000)) - { - // On-Ice - pool_config.status = 3; - // Admin status' all supersede backstop triggered active - } else if pool_config.status % 2 != 0 { - // Active - pool_config.status = 1; } } storage::set_pool_config(e, &pool_config); - - pool_config.status } /// Calculate the threshold for the pool's backstop balance @@ -170,7 +188,7 @@ mod tests { storage::set_admin(&e, &bombadil); storage::set_pool_config(&e, &pool_config); - execute_update_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); @@ -178,7 +196,7 @@ mod tests { } #[test] - #[should_panic(expected = "Error(Contract, #2)")] + #[should_panic(expected = "Error(Contract, #8)")] fn test_set_pool_status_active_blocks_without_backstop_minimum() { let e = Env::default(); e.budget().reset_unlimited(); @@ -217,12 +235,12 @@ mod tests { storage::set_admin(&e, &bombadil); storage::set_pool_config(&e, &pool_config); - execute_update_pool_status(&e, 0); + execute_set_pool_status(&e, 0); }); } #[test] - #[should_panic(expected = "Error(Contract, #2)")] + #[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(); @@ -251,7 +269,7 @@ mod tests { ); backstop_client.deposit(&samwise, &pool_id, &50_000_0000000); backstop_client.update_tkn_val(); - backstop_client.withdraw(&samwise, &pool_id, &30_000_0000000); + backstop_client.queue_withdrawal(&samwise, &pool_id, &30_000_0000000); let pool_config = PoolConfig { oracle: oracle_id, @@ -262,7 +280,7 @@ mod tests { storage::set_admin(&e, &bombadil); storage::set_pool_config(&e, &pool_config); - execute_update_pool_status(&e, 0); + execute_set_pool_status(&e, 0); }); } #[test] @@ -304,7 +322,7 @@ mod tests { storage::set_admin(&e, &bombadil); storage::set_pool_config(&e, &pool_config); - execute_update_pool_status(&e, 2); + execute_set_pool_status(&e, 2); let new_pool_config = storage::get_pool_config(&e); assert_eq!(new_pool_config.status, 2); @@ -312,7 +330,7 @@ mod tests { } #[test] - #[should_panic(expected = "Error(Contract, #2)")] + #[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(); @@ -341,7 +359,7 @@ mod tests { ); backstop_client.deposit(&samwise, &pool_id, &50_000_0000000); backstop_client.update_tkn_val(); - backstop_client.withdraw(&samwise, &pool_id, &40_000_0000000); + backstop_client.queue_withdrawal(&samwise, &pool_id, &40_000_0000000); let pool_config = PoolConfig { oracle: oracle_id, @@ -352,7 +370,7 @@ mod tests { storage::set_admin(&e, &bombadil); storage::set_pool_config(&e, &pool_config); - execute_update_pool_status(&e, 2); + execute_set_pool_status(&e, 2); }); } #[test] @@ -394,12 +412,55 @@ mod tests { storage::set_admin(&e, &bombadil); storage::set_pool_config(&e, &pool_config); - execute_update_pool_status(&e, 4); + 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() { @@ -440,7 +501,7 @@ mod tests { storage::set_admin(&e, &bombadil); storage::set_pool_config(&e, &pool_config); - let status = execute_update_pool_status(&e, 11); + let status = execute_update_pool_status(&e); let new_pool_config = storage::get_pool_config(&e); assert_eq!(new_pool_config.status, status); @@ -487,7 +548,7 @@ mod tests { storage::set_admin(&e, &bombadil); storage::set_pool_config(&e, &pool_config); - let status = execute_update_pool_status(&e, 11); + let status = execute_update_pool_status(&e); let new_pool_config = storage::get_pool_config(&e); assert_eq!(new_pool_config.status, status); @@ -534,7 +595,7 @@ mod tests { storage::set_admin(&e, &bombadil); storage::set_pool_config(&e, &pool_config); - let status = execute_update_pool_status(&e, 11); + let status = execute_update_pool_status(&e); let new_pool_config = storage::get_pool_config(&e); assert_eq!(new_pool_config.status, status); @@ -582,7 +643,7 @@ mod tests { storage::set_admin(&e, &bombadil); storage::set_pool_config(&e, &pool_config); - let status = execute_update_pool_status(&e, 11); + let status = execute_update_pool_status(&e); let new_pool_config = storage::get_pool_config(&e); assert_eq!(new_pool_config.status, status); @@ -630,7 +691,7 @@ mod tests { storage::set_admin(&e, &bombadil); storage::set_pool_config(&e, &pool_config); - let status = execute_update_pool_status(&e, 11); + let status = execute_update_pool_status(&e); let new_pool_config = storage::get_pool_config(&e); assert_eq!(new_pool_config.status, status); @@ -678,7 +739,7 @@ mod tests { storage::set_admin(&e, &bombadil); storage::set_pool_config(&e, &pool_config); - let status = execute_update_pool_status(&e, 11); + let status = execute_update_pool_status(&e); let new_pool_config = storage::get_pool_config(&e); assert_eq!(new_pool_config.status, status); @@ -726,7 +787,7 @@ mod tests { storage::set_admin(&e, &bombadil); storage::set_pool_config(&e, &pool_config); - let status = execute_update_pool_status(&e, 11); + let status = execute_update_pool_status(&e); let new_pool_config = storage::get_pool_config(&e); assert_eq!(new_pool_config.status, status); @@ -773,7 +834,7 @@ mod tests { storage::set_admin(&e, &bombadil); storage::set_pool_config(&e, &pool_config); - let status = execute_update_pool_status(&e, 11); + let status = execute_update_pool_status(&e); let new_pool_config = storage::get_pool_config(&e); assert_eq!(new_pool_config.status, status); @@ -821,7 +882,7 @@ mod tests { storage::set_admin(&e, &bombadil); storage::set_pool_config(&e, &pool_config); - let status = execute_update_pool_status(&e, 11); + let status = execute_update_pool_status(&e); let new_pool_config = storage::get_pool_config(&e); assert_eq!(new_pool_config.status, status); @@ -869,7 +930,7 @@ mod tests { storage::set_admin(&e, &bombadil); storage::set_pool_config(&e, &pool_config); - execute_update_pool_status(&e, 11); + execute_update_pool_status(&e); }); } @@ -914,11 +975,10 @@ mod tests { storage::set_admin(&e, &bombadil); storage::set_pool_config(&e, &pool_config); - let status = execute_update_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, status); - assert_eq!(status, 0); + assert_eq!(new_pool_config.status, 0); }); }