From d58d669b60e7864214b17245c9afce7ffad1f6b4 Mon Sep 17 00:00:00 2001 From: mootz12 Date: Sun, 31 Dec 2023 10:32:03 -0500 Subject: [PATCH 1/6] backstop: chore: add payer and payee sanity checks to deposit and donate --- backstop/src/backstop/deposit.rs | 51 +++++++++- backstop/src/backstop/fund_management.rs | 115 ++++++++++++++++++++++- 2 files changed, 163 insertions(+), 3 deletions(-) diff --git a/backstop/src/backstop/deposit.rs b/backstop/src/backstop/deposit.rs index 482cea65..6f914c92 100644 --- a/backstop/src/backstop/deposit.rs +++ b/backstop/src/backstop/deposit.rs @@ -1,12 +1,15 @@ -use crate::{contract::require_nonnegative, emissions, storage}; +use crate::{contract::require_nonnegative, emissions, storage, BackstopError}; use sep_41_token::TokenClient; -use soroban_sdk::{Address, Env}; +use soroban_sdk::{panic_with_error, Address, Env}; use super::require_is_from_pool_factory; /// Perform a deposit into the backstop module pub fn execute_deposit(e: &Env, from: &Address, pool_address: &Address, amount: i128) -> i128 { require_nonnegative(e, amount); + if from == pool_address || from == &e.current_contract_address() { + panic_with_error!(e, &BackstopError::BadRequest) + } let mut pool_balance = storage::get_pool_balance(e, pool_address); require_is_from_pool_factory(e, pool_address, pool_balance.shares); let mut user_balance = storage::get_user_balance(e, pool_address, from); @@ -141,6 +144,50 @@ mod tests { }); } + #[test] + #[should_panic(expected = "Error(Contract, #1)")] + fn test_execute_deposit_from_is_to() { + let e = Env::default(); + e.mock_all_auths_allowing_non_root_auth(); + + let backstop_address = create_backstop(&e); + let pool_0_id = Address::generate(&e); + let bombadil = Address::generate(&e); + let samwise = Address::generate(&e); + + let (_, backstop_token_client) = create_backstop_token(&e, &backstop_address, &bombadil); + backstop_token_client.mint(&samwise, &100_0000000); + + let (_, mock_pool_factory_client) = create_mock_pool_factory(&e, &backstop_address); + mock_pool_factory_client.set_pool(&pool_0_id); + + e.as_contract(&backstop_address, || { + execute_deposit(&e, &pool_0_id, &pool_0_id, 100); + }); + } + + #[test] + #[should_panic(expected = "Error(Contract, #1)")] + fn test_execute_deposit_from_self() { + let e = Env::default(); + e.mock_all_auths_allowing_non_root_auth(); + + let backstop_address = create_backstop(&e); + let pool_0_id = Address::generate(&e); + let bombadil = Address::generate(&e); + let samwise = Address::generate(&e); + + let (_, backstop_token_client) = create_backstop_token(&e, &backstop_address, &bombadil); + backstop_token_client.mint(&samwise, &100_0000000); + + let (_, mock_pool_factory_client) = create_mock_pool_factory(&e, &backstop_address); + mock_pool_factory_client.set_pool(&pool_0_id); + + e.as_contract(&backstop_address, || { + execute_deposit(&e, &pool_0_id, &pool_0_id, 100); + }); + } + #[test] #[should_panic(expected = "Error(Contract, #10)")] fn text_execute_deposit_not_pool() { diff --git a/backstop/src/backstop/fund_management.rs b/backstop/src/backstop/fund_management.rs index 148b0598..32ea9a18 100644 --- a/backstop/src/backstop/fund_management.rs +++ b/backstop/src/backstop/fund_management.rs @@ -1,9 +1,10 @@ use crate::{ constants::SCALAR_7, contract::require_nonnegative, dependencies::CometClient, storage, + BackstopError, }; use sep_41_token::TokenClient; use soroban_fixed_point_math::FixedPoint; -use soroban_sdk::{unwrap::UnwrapOptimized, Address, Env}; +use soroban_sdk::{panic_with_error, unwrap::UnwrapOptimized, Address, Env}; use super::require_is_from_pool_factory; @@ -23,6 +24,9 @@ pub fn execute_draw(e: &Env, pool_address: &Address, amount: i128, to: &Address) /// Perform a donation to a pool's backstop pub fn execute_donate(e: &Env, from: &Address, pool_address: &Address, amount: i128) { require_nonnegative(e, amount); + if from == pool_address || from == &e.current_contract_address() { + panic_with_error!(e, &BackstopError::BadRequest) + } let mut pool_balance = storage::get_pool_balance(e, pool_address); require_is_from_pool_factory(e, pool_address, pool_balance.shares); @@ -37,6 +41,9 @@ pub fn execute_donate(e: &Env, from: &Address, pool_address: &Address, amount: i /// Perform a donation of USDC to a pool's backstop pub fn execute_donate_usdc(e: &Env, from: &Address, pool_address: &Address, amount: i128) { require_nonnegative(e, amount); + if from == pool_address || from == &e.current_contract_address() { + panic_with_error!(e, &BackstopError::BadRequest) + } let mut pool_usdc = storage::get_pool_usdc(e, pool_address); require_is_from_pool_factory(e, pool_address, pool_usdc); @@ -172,6 +179,66 @@ mod tests { }); } + #[test] + #[should_panic(expected = "Error(Contract, #1)")] + fn test_execute_donate_from_is_to() { + let e = Env::default(); + e.mock_all_auths_allowing_non_root_auth(); + e.budget().reset_unlimited(); + + let backstop_id = create_backstop(&e); + let pool_0_id = Address::generate(&e); + let bombadil = Address::generate(&e); + let samwise = Address::generate(&e); + let frodo = Address::generate(&e); + + let (_, backstop_token_client) = create_backstop_token(&e, &backstop_id, &bombadil); + backstop_token_client.mint(&samwise, &100_0000000); + backstop_token_client.mint(&frodo, &100_0000000); + + let (_, mock_pool_factory_client) = create_mock_pool_factory(&e, &backstop_id); + mock_pool_factory_client.set_pool(&pool_0_id); + + // initialize pool 0 with funds + e.as_contract(&backstop_id, || { + execute_deposit(&e, &frodo, &pool_0_id, 25_0000000); + }); + + e.as_contract(&backstop_id, || { + execute_donate(&e, &pool_0_id, &pool_0_id, 10_0000000); + }); + } + + #[test] + #[should_panic(expected = "Error(Contract, #1)")] + fn test_execute_donate_from_is_self() { + let e = Env::default(); + e.mock_all_auths_allowing_non_root_auth(); + e.budget().reset_unlimited(); + + let backstop_id = create_backstop(&e); + let pool_0_id = Address::generate(&e); + let bombadil = Address::generate(&e); + let samwise = Address::generate(&e); + let frodo = Address::generate(&e); + + let (_, backstop_token_client) = create_backstop_token(&e, &backstop_id, &bombadil); + backstop_token_client.mint(&samwise, &100_0000000); + backstop_token_client.mint(&frodo, &100_0000000); + + let (_, mock_pool_factory_client) = create_mock_pool_factory(&e, &backstop_id); + mock_pool_factory_client.set_pool(&pool_0_id); + + // initialize pool 0 with funds + e.as_contract(&backstop_id, || { + execute_deposit(&e, &frodo, &pool_0_id, 25_0000000); + }); + + e.as_contract(&backstop_id, || { + execute_donate(&e, &backstop_id, &pool_0_id, 10_0000000); + }); + } + #[test] #[should_panic(expected = "Error(Contract, #10)")] fn test_execute_donate_not_pool() { @@ -348,6 +415,52 @@ mod tests { }); } + #[test] + #[should_panic(expected = "Error(Contract, #1)")] + fn test_execute_donate_usdc_from_is_to() { + let e = Env::default(); + e.mock_all_auths_allowing_non_root_auth(); + e.budget().reset_unlimited(); + + let backstop_id = create_backstop(&e); + let pool_0_id = Address::generate(&e); + let bombadil = Address::generate(&e); + let samwise = Address::generate(&e); + + let (_, usdc_token_client) = create_usdc_token(&e, &backstop_id, &bombadil); + usdc_token_client.mint(&samwise, &100_0000000); + + let (_, mock_pool_factory_client) = create_mock_pool_factory(&e, &backstop_id); + mock_pool_factory_client.set_pool(&pool_0_id); + + e.as_contract(&backstop_id, || { + execute_donate_usdc(&e, &pool_0_id, &pool_0_id, 10_0000000); + }); + } + + #[test] + #[should_panic(expected = "Error(Contract, #1)")] + fn test_execute_donate_usdc_from_is_self() { + let e = Env::default(); + e.mock_all_auths_allowing_non_root_auth(); + e.budget().reset_unlimited(); + + let backstop_id = create_backstop(&e); + let pool_0_id = Address::generate(&e); + let bombadil = Address::generate(&e); + let samwise = Address::generate(&e); + + let (_, usdc_token_client) = create_usdc_token(&e, &backstop_id, &bombadil); + usdc_token_client.mint(&samwise, &100_0000000); + + let (_, mock_pool_factory_client) = create_mock_pool_factory(&e, &backstop_id); + mock_pool_factory_client.set_pool(&pool_0_id); + + e.as_contract(&backstop_id, || { + execute_donate_usdc(&e, &backstop_id, &pool_0_id, 10_0000000); + }); + } + #[test] #[should_panic(expected = "Error(Contract, #10)")] fn test_execute_donate_usdc_not_pool() { From a3a50722894ab6c5fca0def86f943f6521723f0a Mon Sep 17 00:00:00 2001 From: mootz12 Date: Sun, 31 Dec 2023 10:47:22 -0500 Subject: [PATCH 2/6] pool: chore: add payee and payer sanity check on requests --- pool/src/pool/submit.rs | 190 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 189 insertions(+), 1 deletion(-) diff --git a/pool/src/pool/submit.rs b/pool/src/pool/submit.rs index f28dbe75..f6dc075d 100644 --- a/pool/src/pool/submit.rs +++ b/pool/src/pool/submit.rs @@ -1,5 +1,7 @@ use sep_41_token::TokenClient; -use soroban_sdk::{Address, Env, Vec}; +use soroban_sdk::{panic_with_error, Address, Env, Vec}; + +use crate::PoolError; use super::{ actions::{build_actions_from_request, Request}, @@ -25,6 +27,12 @@ pub fn execute_submit( to: &Address, requests: Vec, ) -> Positions { + if from == &e.current_contract_address() + || spender == &e.current_contract_address() + || to == &e.current_contract_address() + { + panic_with_error!(e, &PoolError::BadRequest); + } let mut pool = Pool::load(e); let (actions, new_from_state, check_health) = @@ -233,4 +241,184 @@ mod tests { execute_submit(&e, &samwise, &frodo, &merry, requests); }); } + + #[test] + #[should_panic(expected = "Error(Contract, #2)")] + fn test_submit_from_is_not_self() { + let e = Env::default(); + e.budget().reset_unlimited(); + e.mock_all_auths_allowing_non_root_auth(); + + e.ledger().set(LedgerInfo { + timestamp: 600, + protocol_version: 20, + sequence_number: 1234, + 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 = testutils::create_pool(&e); + let (oracle, oracle_client) = testutils::create_mock_oracle(&e); + + let (underlying_0, underlying_0_client) = testutils::create_token_contract(&e, &bombadil); + let (reserve_config, reserve_data) = testutils::default_reserve_meta(); + testutils::create_reserve(&e, &pool, &underlying_0, &reserve_config, &reserve_data); + + underlying_0_client.mint(&samwise, &16_0000000); + + oracle_client.set_data( + &bombadil, + &Asset::Other(Symbol::new(&e, "USD")), + &vec![&e, Asset::Stellar(underlying_0.clone())], + &7, + &300, + ); + oracle_client.set_price_stable(&vec![&e, 1_0000000]); + + let pool_config = PoolConfig { + oracle, + bstop_rate: 0_100_000_000, + status: 0, + max_positions: 2, + }; + e.as_contract(&pool, || { + e.mock_all_auths_allowing_non_root_auth(); + storage::set_pool_config(&e, &pool_config); + + let requests = vec![ + &e, + Request { + request_type: 2, + address: underlying_0, + amount: 15_0000000, + }, + ]; + execute_submit(&e, &pool, &samwise, &samwise, requests); + }); + } + + #[test] + #[should_panic(expected = "Error(Contract, #2)")] + fn test_submit_spender_is_not_self() { + let e = Env::default(); + e.budget().reset_unlimited(); + e.mock_all_auths_allowing_non_root_auth(); + + e.ledger().set(LedgerInfo { + timestamp: 600, + protocol_version: 20, + sequence_number: 1234, + 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 = testutils::create_pool(&e); + let (oracle, oracle_client) = testutils::create_mock_oracle(&e); + + let (underlying_0, underlying_0_client) = testutils::create_token_contract(&e, &bombadil); + let (reserve_config, reserve_data) = testutils::default_reserve_meta(); + testutils::create_reserve(&e, &pool, &underlying_0, &reserve_config, &reserve_data); + + underlying_0_client.mint(&samwise, &16_0000000); + + oracle_client.set_data( + &bombadil, + &Asset::Other(Symbol::new(&e, "USD")), + &vec![&e, Asset::Stellar(underlying_0.clone())], + &7, + &300, + ); + oracle_client.set_price_stable(&vec![&e, 1_0000000]); + + let pool_config = PoolConfig { + oracle, + bstop_rate: 0_100_000_000, + status: 0, + max_positions: 2, + }; + e.as_contract(&pool, || { + e.mock_all_auths_allowing_non_root_auth(); + storage::set_pool_config(&e, &pool_config); + + let requests = vec![ + &e, + Request { + request_type: 2, + address: underlying_0, + amount: 15_0000000, + }, + ]; + execute_submit(&e, &samwise, &pool, &samwise, requests); + }); + } + + #[test] + #[should_panic(expected = "Error(Contract, #2)")] + fn test_submit_to_is_not_self() { + let e = Env::default(); + e.budget().reset_unlimited(); + e.mock_all_auths_allowing_non_root_auth(); + + e.ledger().set(LedgerInfo { + timestamp: 600, + protocol_version: 20, + sequence_number: 1234, + 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 = testutils::create_pool(&e); + let (oracle, oracle_client) = testutils::create_mock_oracle(&e); + + let (underlying_0, underlying_0_client) = testutils::create_token_contract(&e, &bombadil); + let (reserve_config, reserve_data) = testutils::default_reserve_meta(); + testutils::create_reserve(&e, &pool, &underlying_0, &reserve_config, &reserve_data); + + underlying_0_client.mint(&samwise, &16_0000000); + + oracle_client.set_data( + &bombadil, + &Asset::Other(Symbol::new(&e, "USD")), + &vec![&e, Asset::Stellar(underlying_0.clone())], + &7, + &300, + ); + oracle_client.set_price_stable(&vec![&e, 1_0000000]); + + let pool_config = PoolConfig { + oracle, + bstop_rate: 0_100_000_000, + status: 0, + max_positions: 2, + }; + e.as_contract(&pool, || { + e.mock_all_auths_allowing_non_root_auth(); + storage::set_pool_config(&e, &pool_config); + + let requests = vec![ + &e, + Request { + request_type: 2, + address: underlying_0, + amount: 15_0000000, + }, + ]; + execute_submit(&e, &samwise, &samwise, &pool, requests); + }); + } } From 1b008762d601221cd4921bde85c3f204ba7d835d Mon Sep 17 00:00:00 2001 From: mootz12 Date: Sun, 31 Dec 2023 11:17:54 -0500 Subject: [PATCH 3/6] 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 4/6] 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 5/6] 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); + }); + } } From 0a866aa802d7211c8393bca88c65e1ca2ed8a989 Mon Sep 17 00:00:00 2001 From: mootz12 Date: Tue, 2 Jan 2024 13:06:31 -0500 Subject: [PATCH 6/6] backstop: test: fix wrong param in deposit test --- backstop/src/backstop/deposit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backstop/src/backstop/deposit.rs b/backstop/src/backstop/deposit.rs index 6f914c92..2095ddbd 100644 --- a/backstop/src/backstop/deposit.rs +++ b/backstop/src/backstop/deposit.rs @@ -184,7 +184,7 @@ mod tests { mock_pool_factory_client.set_pool(&pool_0_id); e.as_contract(&backstop_address, || { - execute_deposit(&e, &pool_0_id, &pool_0_id, 100); + execute_deposit(&e, &backstop_address, &pool_0_id, 100); }); }