Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 159 add position caps #174

Merged
merged 4 commits into from
Dec 29, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should likely be included with pool_init_meta, right? Like backstopTakeRate, it feels like a vital piece of setup required for the pool to function and should be set on initialization.

In the same vein, it feels appropriate to include this with update_pool or rename that function to set_backstop_rate to avoid confusion.

Having one function that sets multiple pieces of pool configuration doesn't seem problematic to me, but I don't have a strong opinion either way.


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
///
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix comment

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix comment

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