From 1b008762d601221cd4921bde85c3f204ba7d835d Mon Sep 17 00:00:00 2001 From: mootz12 Date: Sun, 31 Dec 2023 11:17:54 -0500 Subject: [PATCH 1/3] backstop: chore: remove unnecessary code seperation in backstop bad debt flow --- pool/src/auctions/bad_debt_auction.rs | 21 ++++++- pool/src/pool/bad_debt.rs | 82 +-------------------------- pool/src/pool/mod.rs | 2 +- 3 files changed, 20 insertions(+), 85 deletions(-) diff --git a/pool/src/auctions/bad_debt_auction.rs b/pool/src/auctions/bad_debt_auction.rs index bc45e685..fdbd615d 100644 --- a/pool/src/auctions/bad_debt_auction.rs +++ b/pool/src/auctions/bad_debt_auction.rs @@ -2,12 +2,12 @@ use crate::{ constants::SCALAR_7, dependencies::BackstopClient, errors::PoolError, - pool::{burn_backstop_bad_debt, calc_pool_backstop_threshold, Pool, User}, + pool::{calc_pool_backstop_threshold, Pool, User}, storage, }; use cast::i128; use soroban_fixed_point_math::FixedPoint; -use soroban_sdk::{map, panic_with_error, unwrap::UnwrapOptimized, Address, Env}; +use soroban_sdk::{map, panic_with_error, unwrap::UnwrapOptimized, Address, Env, Symbol}; use super::{AuctionData, AuctionType}; @@ -99,7 +99,20 @@ pub fn fill_bad_debt_auction( let threshold = calc_pool_backstop_threshold(&pool_backstop_data); if threshold < 0_0000003 { // ~5% of threshold - burn_backstop_bad_debt(e, &mut backstop_state, pool); + let reserve_list = storage::get_res_list(e); + let mut rm_liabilities = map![e]; + for (reserve_index, liability_balance) in backstop_state.positions.liabilities.iter() { + let res_asset_address = reserve_list.get_unchecked(reserve_index); + rm_liabilities.set(res_asset_address.clone(), liability_balance); + + e.events().publish( + (Symbol::new(e, "bad_debt"), backstop_address.clone()), + (res_asset_address, liability_balance), + ); + } + // remove liability debtTokens from backstop resulting in a shared loss for + // token suppliers + backstop_state.rm_positions(e, pool, map![e], rm_liabilities); } } backstop_state.store(e); @@ -885,6 +898,8 @@ mod tests { ); let backstop_positions = storage::get_user_positions(&e, &backstop_address); assert_eq!(backstop_positions.liabilities.len(), 0); + assert_eq!(backstop_positions.collateral.len(), 0); + assert_eq!(backstop_positions.supply.len(), 0); }); } diff --git a/pool/src/pool/bad_debt.rs b/pool/src/pool/bad_debt.rs index 0af51e7d..99fa806f 100644 --- a/pool/src/pool/bad_debt.rs +++ b/pool/src/pool/bad_debt.rs @@ -1,4 +1,4 @@ -use soroban_sdk::{map, panic_with_error, Address, Env, Symbol}; +use soroban_sdk::{panic_with_error, Address, Env, Symbol}; use crate::{ errors::PoolError, @@ -51,24 +51,6 @@ pub fn transfer_bad_debt_to_backstop(e: &Env, user: &Address) { new_user_state.store(e); } -/// Burn bad debt from the backstop. This can only occur if the backstop module has reached a critical balance -pub fn burn_backstop_bad_debt(e: &Env, backstop: &mut User, pool: &mut Pool) { - let reserve_list = storage::get_res_list(e); - let mut rm_liabilities = map![e]; - for (reserve_index, liability_balance) in backstop.positions.liabilities.iter() { - let res_asset_address = reserve_list.get_unchecked(reserve_index); - rm_liabilities.set(res_asset_address.clone(), liability_balance); - - e.events().publish( - (Symbol::new(e, "bad_debt"), backstop.address.clone()), - (res_asset_address, liability_balance), - ); - } - // remove liability debtTokens from backstop resulting in a shared loss for - // token suppliers - backstop.rm_positions(e, pool, map![e], rm_liabilities); -} - #[cfg(test)] mod tests { use crate::{pool::Positions, storage::PoolConfig, testutils}; @@ -301,66 +283,4 @@ mod tests { transfer_bad_debt_to_backstop(&e, &backstop); }); } - - #[test] - fn test_burn_backstop_bad_debt() { - let e = Env::default(); - e.mock_all_auths(); - - e.ledger().set(LedgerInfo { - timestamp: 1500000000, - protocol_version: 20, - sequence_number: 123, - network_id: Default::default(), - base_reserve: 10, - min_temp_entry_ttl: 10, - min_persistent_entry_ttl: 10, - max_entry_ttl: 2000000, - }); - - let bombadil = Address::generate(&e); - let pool = testutils::create_pool(&e); - let backstop = Address::generate(&e); - - let (_, blnd_client) = testutils::create_blnd_token(&e, &pool, &bombadil); - - let (underlying_0, _) = testutils::create_token_contract(&e, &bombadil); - let (reserve_config, mut reserve_data) = testutils::default_reserve_meta(); - reserve_data.last_time = 1499995000; - testutils::create_reserve(&e, &pool, &underlying_0, &reserve_config, &reserve_data); - - let (underlying_1, _) = testutils::create_token_contract(&e, &bombadil); - let (mut reserve_config, mut reserve_data) = testutils::default_reserve_meta(); - reserve_config.index = 1; - reserve_data.last_time = 1499995000; - testutils::create_reserve(&e, &pool, &underlying_1, &reserve_config, &reserve_data); - - blnd_client.mint(&backstop, &123); - - let pool_config = PoolConfig { - oracle: Address::generate(&e), - bstop_rate: 0_100_000_000, - status: 0, - max_positions: 2, - }; - - let backstop_positions = Positions { - liabilities: map![&e, (0, 24_0000000), (1, 25_0000000)], - collateral: map![&e], - supply: map![&e], - }; - e.as_contract(&pool, || { - storage::set_pool_config(&e, &pool_config); - storage::set_backstop(&e, &backstop); - storage::set_user_positions(&e, &backstop, &backstop_positions); - - let mut pool_obj = Pool::load(&e); - let mut backstop_user = User::load(&e, &backstop); - e.budget().reset_unlimited(); - burn_backstop_bad_debt(&e, &mut backstop_user, &mut pool_obj); - - assert_eq!(backstop_user.positions.collateral.len(), 0); - assert_eq!(backstop_user.positions.liabilities.len(), 0); - }); - } } diff --git a/pool/src/pool/mod.rs b/pool/src/pool/mod.rs index 9097e5e1..01c7b3dc 100644 --- a/pool/src/pool/mod.rs +++ b/pool/src/pool/mod.rs @@ -2,7 +2,7 @@ mod actions; pub use actions::Request; mod bad_debt; -pub use bad_debt::{burn_backstop_bad_debt, transfer_bad_debt_to_backstop}; +pub use bad_debt::transfer_bad_debt_to_backstop; mod config; pub use config::{ From f55e57b4ba749164d416b2885901a5606370be3f Mon Sep 17 00:00:00 2001 From: mootz12 Date: Mon, 1 Jan 2024 10:26:48 -0500 Subject: [PATCH 2/3] pool: chore: move validation of auction fill pct to scale auction --- pool/src/auctions/auction.rs | 67 +++++++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 4 deletions(-) diff --git a/pool/src/auctions/auction.rs b/pool/src/auctions/auction.rs index 15a8d952..8c1e3e6e 100644 --- a/pool/src/auctions/auction.rs +++ b/pool/src/auctions/auction.rs @@ -130,10 +130,6 @@ pub fn fill( percent_filled: u64, ) { let auction_data = storage::get_auction(e, &auction_type, user); - if percent_filled > 100 || percent_filled == 0 { - panic_with_error!(e, PoolError::BadRequest); - } - let (to_fill_auction, remaining_auction) = scale_auction(e, &auction_data, percent_filled); match AuctionType::from_u32(auction_type) { AuctionType::UserLiquidation => { @@ -164,12 +160,19 @@ pub fn fill( /// Returns the (Scaled Auction, Remaining Auction) such that: /// - Scaled Auction is the auction data scaled /// - Remaining Auction is the leftover auction data that will be stored in the ledger, or deleted if None +/// +/// ### Panics +/// If the percent filled is greater than 100 or less than 0 #[allow(clippy::zero_prefixed_literal)] fn scale_auction( e: &Env, auction_data: &AuctionData, percent_filled: u64, ) -> (AuctionData, Option) { + if percent_filled > 100 || percent_filled == 0 { + panic_with_error!(e, PoolError::BadRequest); + } + let mut to_fill_auction = AuctionData { bid: map![e], lot: map![e], @@ -1577,4 +1580,60 @@ mod tests { 12_5000003 ); } + + #[test] + #[should_panic(expected = "Error(Contract, #2)")] + fn test_scale_auction_fill_percentage_zero() { + let e = Env::default(); + let underlying_0 = Address::generate(&e); + let underlying_1 = Address::generate(&e); + + let base_auction_data = AuctionData { + bid: map![&e, (underlying_0.clone(), 25_0000005)], + lot: map![&e, (underlying_1.clone(), 25_0000005)], + block: 1000, + }; + + // 0 blocks + e.ledger().set(LedgerInfo { + timestamp: 12345, + protocol_version: 20, + sequence_number: 1000, + network_id: Default::default(), + base_reserve: 10, + min_temp_entry_ttl: 172800, + min_persistent_entry_ttl: 172800, + max_entry_ttl: 9999999, + }); + + let (_, _) = scale_auction(&e, &base_auction_data, 0); + } + + #[test] + #[should_panic(expected = "Error(Contract, #2)")] + fn test_scale_auction_fill_percentage_over_100() { + let e = Env::default(); + let underlying_0 = Address::generate(&e); + let underlying_1 = Address::generate(&e); + + let base_auction_data = AuctionData { + bid: map![&e, (underlying_0.clone(), 25_0000005)], + lot: map![&e, (underlying_1.clone(), 25_0000005)], + block: 1000, + }; + + // 0 blocks + e.ledger().set(LedgerInfo { + timestamp: 12345, + protocol_version: 20, + sequence_number: 1000, + network_id: Default::default(), + base_reserve: 10, + min_temp_entry_ttl: 172800, + min_persistent_entry_ttl: 172800, + max_entry_ttl: 9999999, + }); + + let (_, _) = scale_auction(&e, &base_auction_data, 101); + } } From 43bca8ff2f2159cc5360ac52159654b9cf7f0937 Mon Sep 17 00:00:00 2001 From: mootz12 Date: Mon, 1 Jan 2024 11:25:03 -0500 Subject: [PATCH 3/3] pool: chore: add extra verification to prevent backstop from filling auctions --- .../src/auctions/backstop_interest_auction.rs | 94 +++++++++++++ pool/src/auctions/bad_debt_auction.rs | 127 ++++++++++++++++++ 2 files changed, 221 insertions(+) diff --git a/pool/src/auctions/backstop_interest_auction.rs b/pool/src/auctions/backstop_interest_auction.rs index 962062a1..91cb65e9 100644 --- a/pool/src/auctions/backstop_interest_auction.rs +++ b/pool/src/auctions/backstop_interest_auction.rs @@ -67,6 +67,9 @@ pub fn fill_interest_auction( ) { // bid only contains the USDC token let backstop = storage::get_backstop(e); + if filler.clone() == backstop { + panic_with_error!(e, PoolError::BadRequest); + } let usdc_token = storage::get_usdc_token(e); let usdc_bid_amount = auction_data.bid.get_unchecked(usdc_token); @@ -587,4 +590,95 @@ mod tests { assert_eq!(reserve_1_data.backstop_credit, 5_0000000); }); } + + #[test] + #[should_panic(expected = "Error(Contract, #2)")] + fn test_fill_interest_auction_with_backstop() { + let e = Env::default(); + e.mock_all_auths_allowing_non_root_auth(); + e.budget().reset_unlimited(); + + e.ledger().set(LedgerInfo { + timestamp: 12345, + protocol_version: 20, + sequence_number: 301, + network_id: Default::default(), + base_reserve: 10, + min_temp_entry_ttl: 10, + min_persistent_entry_ttl: 10, + max_entry_ttl: 2000000, + }); + + let bombadil = Address::generate(&e); + let samwise = Address::generate(&e); + + let pool_address = create_pool(&e); + + let (usdc_id, usdc_client) = testutils::create_usdc_token(&e, &pool_address, &bombadil); + let (backstop_address, _backstop_client) = testutils::create_backstop(&e); + testutils::setup_backstop( + &e, + &pool_address, + &backstop_address, + &Address::generate(&e), + &usdc_id, + &Address::generate(&e), + ); + + let (underlying_0, underlying_0_client) = testutils::create_token_contract(&e, &bombadil); + let (mut reserve_config_0, reserve_data_0) = testutils::default_reserve_meta(); + reserve_config_0.index = 0; + testutils::create_reserve( + &e, + &pool_address, + &underlying_0, + &reserve_config_0, + &reserve_data_0, + ); + underlying_0_client.mint(&pool_address, &1_000_0000000); + + let (underlying_1, underlying_1_client) = testutils::create_token_contract(&e, &bombadil); + let (mut reserve_config_1, reserve_data_1) = testutils::default_reserve_meta(); + reserve_config_1.index = 1; + testutils::create_reserve( + &e, + &pool_address, + &underlying_1, + &reserve_config_1, + &reserve_data_1, + ); + underlying_1_client.mint(&pool_address, &1_000_0000000); + + let pool_config = PoolConfig { + oracle: Address::generate(&e), + bstop_rate: 0_100_000_000, + status: 0, + max_positions: 4, + }; + let mut auction_data = AuctionData { + bid: map![&e, (usdc_id.clone(), 95_0000000)], + lot: map![ + &e, + (underlying_0.clone(), 100_0000000), + (underlying_1.clone(), 25_0000000) + ], + block: 51, + }; + usdc_client.mint(&samwise, &100_0000000); + e.as_contract(&pool_address, || { + e.mock_all_auths_allowing_non_root_auth(); + storage::set_auction( + &e, + &(AuctionType::InterestAuction as u32), + &backstop_address, + &auction_data, + ); + storage::set_pool_config(&e, &pool_config); + storage::set_backstop(&e, &backstop_address); + storage::set_usdc_token(&e, &usdc_id); + + let mut pool = Pool::load(&e); + fill_interest_auction(&e, &mut pool, &mut auction_data, &backstop_address); + }); + } } diff --git a/pool/src/auctions/bad_debt_auction.rs b/pool/src/auctions/bad_debt_auction.rs index fdbd615d..5bdb7d43 100644 --- a/pool/src/auctions/bad_debt_auction.rs +++ b/pool/src/auctions/bad_debt_auction.rs @@ -77,6 +77,9 @@ pub fn fill_bad_debt_auction( filler_state: &mut User, ) { let backstop_address = storage::get_backstop(e); + if filler_state.address == backstop_address { + panic_with_error!(e, PoolError::BadRequest); + } let mut backstop_state = User::load(e, &backstop_address); // bid only contains d_token asset amounts @@ -1065,4 +1068,128 @@ mod tests { ); }); } + + #[test] + #[should_panic(expected = "Error(Contract, #2)")] + fn test_fill_bad_debt_auction_with_backstop() { + let e = Env::default(); + e.mock_all_auths_allowing_non_root_auth(); + e.budget().reset_unlimited(); // setup exhausts budget + + e.ledger().set(LedgerInfo { + timestamp: 12345, + protocol_version: 20, + sequence_number: 51, + network_id: Default::default(), + base_reserve: 10, + min_temp_entry_ttl: 10, + min_persistent_entry_ttl: 10, + max_entry_ttl: 2000000, + }); + + let bombadil = Address::generate(&e); + let samwise = Address::generate(&e); + + let pool_address = create_pool(&e); + + let (blnd, blnd_client) = testutils::create_blnd_token(&e, &pool_address, &bombadil); + let (usdc, usdc_client) = testutils::create_usdc_token(&e, &pool_address, &bombadil); + let (lp_token, lp_token_client) = + testutils::create_comet_lp_pool(&e, &bombadil, &blnd, &usdc); + let (backstop_address, backstop_client) = testutils::create_backstop(&e); + testutils::setup_backstop( + &e, + &pool_address, + &backstop_address, + &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_address, &50_000_0000000); + backstop_client.update_tkn_val(); + + let (underlying_0, _) = testutils::create_token_contract(&e, &bombadil); + let (mut reserve_config_0, mut reserve_data_0) = testutils::default_reserve_meta(); + reserve_data_0.d_rate = 1_100_000_000; + reserve_data_0.last_time = 12345; + reserve_config_0.index = 0; + testutils::create_reserve( + &e, + &pool_address, + &underlying_0, + &reserve_config_0, + &reserve_data_0, + ); + + let (underlying_1, _) = testutils::create_token_contract(&e, &bombadil); + let (mut reserve_config_1, mut reserve_data_1) = testutils::default_reserve_meta(); + reserve_data_1.d_rate = 1_200_000_000; + reserve_data_1.last_time = 12345; + reserve_config_1.index = 1; + testutils::create_reserve( + &e, + &pool_address, + &underlying_1, + &reserve_config_1, + &reserve_data_1, + ); + + let (underlying_2, _) = testutils::create_token_contract(&e, &bombadil); + let (mut reserve_config_2, mut reserve_data_2) = testutils::default_reserve_meta(); + reserve_data_2.b_rate = 1_100_000_000; + reserve_data_2.last_time = 12345; + reserve_config_2.index = 1; + testutils::create_reserve( + &e, + &pool_address, + &underlying_2, + &reserve_config_2, + &reserve_data_2, + ); + let pool_config = PoolConfig { + oracle: Address::generate(&e), + bstop_rate: 0_100_000_000, + status: 0, + max_positions: 4, + }; + let mut auction_data = AuctionData { + bid: map![&e, (underlying_0, 10_0000000), (underlying_1, 2_5000000)], + lot: map![&e, (lp_token.clone(), 47_6000000)], + block: 51, + }; + let positions: Positions = Positions { + collateral: map![&e], + liabilities: map![ + &e, + (reserve_config_0.index, 10_0000000), + (reserve_config_1.index, 2_5000000) + ], + supply: map![&e], + }; + + e.as_contract(&pool_address, || { + storage::set_auction( + &e, + &(AuctionType::BadDebtAuction as u32), + &backstop_address, + &auction_data, + ); + storage::set_pool_config(&e, &pool_config); + storage::set_user_positions(&e, &backstop_address, &positions); + + let mut pool = Pool::load(&e); + let mut backstop_state = User::load(&e, &backstop_address); + fill_bad_debt_auction(&e, &mut pool, &mut auction_data, &mut backstop_state); + }); + } }