Skip to content

Commit

Permalink
Pool: added max position management
Browse files Browse the repository at this point in the history
  • Loading branch information
markuspluna committed Dec 21, 2023
1 parent 7ce666b commit 8f41dff
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 2 deletions.
20 changes: 20 additions & 0 deletions pool/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ pub trait Pool {
/// If the caller is not the admin
fn set_admin(e: Env, new_admin: Address);

/// (Admin only) Set a max number of positions a single user can have
///
/// ### Arguments
/// * `max` - Max number of positions a single user can have
///
/// ### Panics
/// If the caller is not the admin
fn set_max_positions(e: Env, max: u32);

/// (Admin only) Update the pool
///
/// ### Arguments
Expand Down Expand Up @@ -271,6 +280,17 @@ impl Pool for PoolContract {
.publish((Symbol::new(&e, "update_pool"), admin), backstop_take_rate);
}

fn set_max_positions(e: Env, max: u32) {
storage::extend_instance(&e);
let admin = storage::get_admin(&e);
admin.require_auth();

storage::set_max_positions(&e, &max);

e.events()
.publish((Symbol::new(&e, "set_max_positions"), admin), max);
}

fn queue_set_reserve(e: Env, asset: Address, metadata: ReserveConfig) {
storage::extend_instance(&e);
let admin = storage::get_admin(&e);
Expand Down
1 change: 1 addition & 0 deletions pool/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub enum PoolError {
InvalidHf = 10,
InvalidPoolStatus = 11,
InvalidUtilRate = 12,
MaxPositionsExceeded = 13,
// Emission Errors (20-29)
EmissionFailure = 20,
// Oracle Errors (30-39)
Expand Down
76 changes: 76 additions & 0 deletions pool/src/pool/submit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ pub fn execute_submit(
TokenClient::new(e, &address).transfer(spender, &e.current_contract_address(), &amount);
}

// ensure user is under max positions
new_from_state.positions.require_under_max(e);

// store updated info to ledger
pool.store_cached_reserves(e);
new_from_state.store(e);
Expand Down Expand Up @@ -121,6 +124,7 @@ mod tests {
};
e.as_contract(&pool, || {
e.mock_all_auths_allowing_non_root_auth();
storage::set_max_positions(&e, &2);
storage::set_pool_config(&e, &pool_config);

let pre_pool_balance_0 = underlying_0_client.balance(&pool);
Expand Down Expand Up @@ -160,7 +164,79 @@ mod tests {
assert_eq!(underlying_1_client.balance(&merry), 1_5000000);
});
}
#[test]
#[should_panic(expected = "Error(Contract, #13)")]
fn test_submit_requires_positions_under_max() {
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 frodo = Address::generate(&e);
let merry = 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);

let (underlying_1, underlying_1_client) = testutils::create_token_contract(&e, &bombadil);
let (reserve_config, reserve_data) = testutils::default_reserve_meta();
testutils::create_reserve(&e, &pool, &underlying_1, &reserve_config, &reserve_data);

underlying_0_client.mint(&frodo, &16_0000000);

oracle_client.set_data(
&bombadil,
&Asset::Other(Symbol::new(&e, "USD")),
&vec![
&e,
Asset::Stellar(underlying_0.clone()),
Asset::Stellar(underlying_1.clone()),
],
&7,
&300,
);
oracle_client.set_price_stable(&vec![&e, 1_0000000, 5_0000000]);

let pool_config = PoolConfig {
oracle,
bstop_rate: 0_100_000_000,
status: 0,
};
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,
},
Request {
request_type: 4,
address: underlying_1,
amount: 1_5000000,
},
];
execute_submit(&e, &samwise, &frodo, &merry, requests);
});
}
#[test]
#[should_panic(expected = "Error(Contract, #10)")]
fn test_submit_requires_healhty() {
Expand Down
66 changes: 64 additions & 2 deletions pool/src/pool/user.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use soroban_sdk::{contracttype, Address, Env, Map};
use soroban_sdk::{contracttype, panic_with_error, Address, Env, Map};

use crate::{emissions, storage, validator::require_nonnegative};
use crate::{emissions, storage, validator::require_nonnegative, PoolError};

use super::{Pool, Reserve};

Expand All @@ -22,6 +22,11 @@ impl Positions {
supply: Map::new(e),
}
}
pub fn require_under_max(&self, e: &Env) {
if storage::get_max_positions(e) < self.liabilities.len() + self.collateral.len() as u32 {
panic_with_error!(e, PoolError::MaxPositionsExceeded)
}
}
}

/// A user / contracts position's with the pool
Expand Down Expand Up @@ -855,4 +860,61 @@ mod tests {
assert_eq!(user.get_total_supply(1), 456 + 789);
});
}

#[test]
fn test_require_under_max_passes() {
let e = Env::default();
let samwise = Address::generate(&e);
let pool = testutils::create_pool(&e);

let mut reserve_0 = testutils::default_reserve(&e);

let mut user = User {
address: samwise.clone(),
positions: Positions::env_default(&e),
};
e.as_contract(&pool, || {
storage::set_max_positions(&e, &1);
user.add_collateral(&e, &mut reserve_0, 123);
user.positions.require_under_max(&e);
});
}
#[test]
#[should_panic(expected = "Error(Contract, #13)")]
fn test_require_under_max_fails() {
let e = Env::default();
let samwise = Address::generate(&e);
let pool = testutils::create_pool(&e);

let mut reserve_0 = testutils::default_reserve(&e);

let mut user = User {
address: samwise.clone(),
positions: Positions::env_default(&e),
};
e.as_contract(&pool, || {
storage::set_max_positions(&e, &1);
user.add_collateral(&e, &mut reserve_0, 123);
user.add_liabilities(&e, &mut reserve_0, 789);
user.positions.require_under_max(&e);
});
}
#[test]
#[should_panic(expected = "Error(Contract, #13)")]
fn test_require_under_max_fails_unset() {
let e = Env::default();
let samwise = Address::generate(&e);
let pool = testutils::create_pool(&e);

let mut reserve_0 = testutils::default_reserve(&e);

let mut user = User {
address: samwise.clone(),
positions: Positions::env_default(&e),
};
e.as_contract(&pool, || {
user.add_collateral(&e, &mut reserve_0, 123);
user.positions.require_under_max(&e);
});
}
}
21 changes: 21 additions & 0 deletions pool/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ pub struct UserEmissionData {
/********** Storage Key Types **********/

const ADMIN_KEY: &str = "Admin";
const MAX_POSITIONS_KEY: &str = "MaxPos";
const NAME_KEY: &str = "Name";
const BACKSTOP_KEY: &str = "Backstop";
const BLND_TOKEN_KEY: &str = "BLNDTkn";
Expand Down Expand Up @@ -227,6 +228,26 @@ pub fn has_admin(e: &Env) -> bool {
e.storage().instance().has(&Symbol::new(e, ADMIN_KEY))
}

/*********** Max Positions ***********/
// Fetch the current max posoitions
///
pub fn get_max_positions(e: &Env) -> u32 {
e.storage()
.instance()
.get(&Symbol::new(e, MAX_POSITIONS_KEY))
.unwrap_or(0)
}

/// Set a new admin
///
/// ### Arguments
/// * `max_positions` - The max positions for the pool
pub fn set_max_positions(e: &Env, max_positions: &u32) {
e.storage()
.instance()
.set::<Symbol, u32>(&Symbol::new(e, MAX_POSITIONS_KEY), max_positions);
}

/********** Metadata **********/

/// Set a pool name
Expand Down
3 changes: 3 additions & 0 deletions test-suites/src/setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ pub fn create_fixture_with_data<'a>(wasm: bool) -> TestFixture<'a> {
];
pool_fixture.pool.set_emissions_config(&reserve_emissions);

// set max positions for pool
pool_fixture.pool.set_max_positions(&6);

// deposit into backstop, add to reward zone
fixture
.backstop
Expand Down

0 comments on commit 8f41dff

Please sign in to comment.