From 5d58cd41b2be3c4cbc6713ffc26306e091d55350 Mon Sep 17 00:00:00 2001 From: markuspluna <59978114+markuspluna@users.noreply.github.com> Date: Tue, 12 Dec 2023 20:20:31 -0800 Subject: [PATCH 1/8] Pool: Added reserve init queues --- emitter/src/storage.rs | 2 +- pool-factory/src/pool_factory.rs | 5 +- pool-factory/src/storage.rs | 18 ++++ pool-factory/src/test.rs | 54 ++++++++++-- pool/src/constants.rs | 3 + pool/src/contract.rs | 62 ++++++++++++-- pool/src/errors.rs | 1 + pool/src/pool/config.rs | 137 ++++++++++++++++++++++++++++++- pool/src/pool/mod.rs | 3 +- pool/src/storage.rs | 53 ++++++++++++ test-suites/src/test_fixture.rs | 7 +- test-suites/tests/test_pool.rs | 5 +- 12 files changed, 326 insertions(+), 24 deletions(-) diff --git a/emitter/src/storage.rs b/emitter/src/storage.rs index 59c07249..1f68bfa6 100644 --- a/emitter/src/storage.rs +++ b/emitter/src/storage.rs @@ -101,7 +101,7 @@ pub fn set_queued_swap(e: &Env, swap: &Swap) { ); } -/// Fetch the current queued backstop swap, or None +/// Delete the current queued backstop swap pub fn del_queued_swap(e: &Env) { e.storage().persistent().remove(&Symbol::new(e, SWAP_KEY)); } diff --git a/pool-factory/src/pool_factory.rs b/pool-factory/src/pool_factory.rs index f8fbdea8..b97daa3e 100644 --- a/pool-factory/src/pool_factory.rs +++ b/pool-factory/src/pool_factory.rs @@ -1,6 +1,6 @@ use crate::{ errors::PoolFactoryError, - storage::{self, PoolInitMeta}, + storage::{self, PoolInitMeta, ReserveConfig}, }; use soroban_sdk::{ contract, contractclient, contractimpl, panic_with_error, vec, Address, BytesN, Env, IntoVal, @@ -32,6 +32,7 @@ pub trait PoolFactory { salt: BytesN<32>, oracle: Address, backstop_take_rate: u64, + reserves: Vec<(Address, ReserveConfig)>, ) -> Address; /// Checks if contract address was deployed by the factory @@ -60,6 +61,7 @@ impl PoolFactory for PoolFactoryContract { salt: BytesN<32>, oracle: Address, backstop_take_rate: u64, + reserves: Vec<(Address, ReserveConfig)>, ) -> Address { admin.require_auth(); storage::extend_instance(&e); @@ -78,6 +80,7 @@ impl PoolFactory for PoolFactoryContract { init_args.push_back(pool_init_meta.backstop.to_val()); init_args.push_back(pool_init_meta.blnd_id.to_val()); init_args.push_back(pool_init_meta.usdc_id.to_val()); + init_args.push_back(reserves.to_val()); let pool_address = e .deployer() .with_current_contract(salt) diff --git a/pool-factory/src/storage.rs b/pool-factory/src/storage.rs index ad726e96..00dceb0d 100644 --- a/pool-factory/src/storage.rs +++ b/pool-factory/src/storage.rs @@ -4,6 +4,24 @@ use soroban_sdk::{contracttype, unwrap::UnwrapOptimized, Address, BytesN, Env, S pub(crate) const LEDGER_THRESHOLD: u32 = 518400; // TODO: Check on phase 1 max ledger entry bump pub(crate) const LEDGER_BUMP: u32 = 535670; // TODO: Check on phase 1 max ledger entry bump +/// The configuration information about a reserve asset +/// TODO: Duplicated from pool/storage.rs. Can this be moved to a common location? +#[derive(Clone)] +#[contracttype] +pub struct ReserveConfig { + pub index: u32, // the index of the reserve in the list + pub decimals: u32, // the decimals used in both the bToken and underlying contract + pub c_factor: u32, // the collateral factor for the reserve scaled expressed in 7 decimals + pub l_factor: u32, // the liability factor for the reserve scaled expressed in 7 decimals + pub util: u32, // the target utilization rate scaled expressed in 7 decimals + pub max_util: u32, // the maximum allowed utilization rate scaled expressed in 7 decimals + pub r_one: u32, // the R1 value in the interest rate formula scaled expressed in 7 decimals + pub r_two: u32, // the R2 value in the interest rate formula scaled expressed in 7 decimals + pub r_three: u32, // the R3 value in the interest rate formula scaled expressed in 7 decimals + pub reactivity: u32, // the reactivity constant for the reserve scaled expressed in 9 decimals +} + + #[derive(Clone)] #[contracttype] pub enum PoolFactoryDataKey { diff --git a/pool-factory/src/test.rs b/pool-factory/src/test.rs index aabe5228..917dd18e 100644 --- a/pool-factory/src/test.rs +++ b/pool-factory/src/test.rs @@ -5,7 +5,10 @@ use soroban_sdk::{ vec, Address, BytesN, Env, IntoVal, Symbol, }; -use crate::{PoolFactoryClient, PoolFactoryContract, PoolInitMeta}; +use crate::{ + storage::ReserveConfig, test::pool::PoolDataKey, PoolFactoryClient, PoolFactoryContract, + PoolInitMeta, +}; mod pool { soroban_sdk::contractimport!(file = "../target/wasm32-unknown-unknown/optimized/pool.wasm"); @@ -48,8 +51,27 @@ fn test_pool_factory() { let name1 = Symbol::new(&e, "pool1"); let name2 = Symbol::new(&e, "pool2"); let salt = BytesN::<32>::random(&e); - let deployed_pool_address_1 = - pool_factory_client.deploy(&bombadil, &name1, &salt, &oracle, &backstop_rate); + let asset_id_0 = Address::generate(&e); + let metadata = ReserveConfig { + index: 0, + decimals: 7, + c_factor: 0_7500000, + l_factor: 0_7500000, + util: 0_5000000, + max_util: 0_9500000, + r_one: 0_0500000, + r_two: 0_5000000, + r_three: 1_5000000, + reactivity: 100, + }; + let deployed_pool_address_1 = pool_factory_client.deploy( + &bombadil, + &name1, + &salt, + &oracle, + &backstop_rate, + &vec![&e, (asset_id_0.clone(), metadata.clone())], + ); let event = vec![&e, e.events().all().last_unchecked()]; assert_eq!( @@ -65,8 +87,14 @@ fn test_pool_factory() { ); let salt = BytesN::<32>::random(&e); - let deployed_pool_address_2 = - pool_factory_client.deploy(&bombadil, &name2, &salt, &oracle, &backstop_rate); + let deployed_pool_address_2 = pool_factory_client.deploy( + &bombadil, + &name2, + &salt, + &oracle, + &backstop_rate, + &vec![&e, (asset_id_0.clone(), metadata.clone())], + ); e.as_contract(&deployed_pool_address_1, || { assert_eq!( @@ -108,6 +136,22 @@ fn test_pool_factory() { .unwrap(), usdc_id.clone() ); + let key = PoolDataKey::ResConfig(asset_id_0.clone()); + let set_config = e + .storage() + .persistent() + .get::<_, ReserveConfig>(&key) + .unwrap(); + assert_eq!(set_config.decimals, metadata.decimals); + assert_eq!(set_config.c_factor, metadata.c_factor); + assert_eq!(set_config.l_factor, metadata.l_factor); + assert_eq!(set_config.util, metadata.util); + assert_eq!(set_config.max_util, metadata.max_util); + assert_eq!(set_config.r_one, metadata.r_one); + assert_eq!(set_config.r_two, metadata.r_two); + assert_eq!(set_config.r_three, metadata.r_three); + assert_eq!(set_config.reactivity, metadata.reactivity); + assert_eq!(set_config.index, 0); }); assert_ne!(deployed_pool_address_1, deployed_pool_address_2); assert!(pool_factory_client.is_pool(&deployed_pool_address_1)); diff --git a/pool/src/constants.rs b/pool/src/constants.rs index 1eb11ab6..2d415947 100644 --- a/pool/src/constants.rs +++ b/pool/src/constants.rs @@ -8,3 +8,6 @@ pub const SCALAR_7: i128 = 1_0000000; // seconds per year pub const SECONDS_PER_YEAR: i128 = 31536000; + +// approximate week in blocks assuming 5 seconds per block +pub const WEEK_IN_BLOCKS: u32 = 120960; diff --git a/pool/src/contract.rs b/pool/src/contract.rs index 6c77851f..a9d74bf8 100644 --- a/pool/src/contract.rs +++ b/pool/src/contract.rs @@ -1,8 +1,9 @@ use crate::{ auctions::{self, AuctionData}, + constants::WEEK_IN_BLOCKS, emissions::{self, ReserveEmissionMetadata}, pool::{self, Positions, Request}, - storage::{self, ReserveConfig}, + storage::{self, QueuedReserveInit, ReserveConfig}, }; use soroban_sdk::{contract, contractclient, contractimpl, Address, Env, Symbol, Vec}; @@ -37,6 +38,7 @@ pub trait Pool { backstop_id: Address, blnd_id: Address, usdc_id: Address, + reserves: Vec<(Address, ReserveConfig)>, ); /// (Admin only) Set a new address as the admin of this pool @@ -57,15 +59,35 @@ pub trait Pool { /// If the caller is not the admin fn update_pool(e: Env, backstop_take_rate: u64); - /// (Admin only) Initialize a reserve in the pool + /// (Admin only) Queues the initialization of a reserve in the pool /// /// ### Arguments /// * `asset` - The underlying asset to add as a reserve /// * `config` - The ReserveConfig for the reserve /// /// ### Panics - /// If the caller is not the admin or the reserve is already setup - fn init_reserve(e: Env, asset: Address, metadata: ReserveConfig) -> u32; + /// If the caller is not the admin + fn queue_init_reserve(e: Env, asset: Address, metadata: ReserveConfig); + + /// (Admin only) Cancels the queued initialization of a reserve in the pool + /// + /// ### Arguments + /// * `asset` - The underlying asset to add as a reserve + /// + /// ### Panics + /// If the caller is not the admin or the reserve is not queued for initialization + fn cancel_init_reserve(e: Env, asset: Address); + + /// (Admin only) Executes the queued initialization of a reserve in the pool + /// + /// ### Arguments + /// * `asset` - The underlying asset to add as a reserve + /// + /// ### Panics + /// If the reserve is not queued for initialization + /// or is already setup + /// or has invalid metadata + fn init_reserve(e: Env, asset: Address) -> u32; /// (Admin only) Update a reserve in the pool /// @@ -224,6 +246,7 @@ impl Pool for PoolContract { backstop_id: Address, blnd_id: Address, usdc_id: Address, + reserves: Vec<(Address, ReserveConfig)>, ) { storage::extend_instance(&e); @@ -236,6 +259,7 @@ impl Pool for PoolContract { &backstop_id, &blnd_id, &usdc_id, + &reserves, ); } @@ -261,15 +285,39 @@ impl Pool for PoolContract { .publish((Symbol::new(&e, "update_pool"), admin), backstop_take_rate); } - fn init_reserve(e: Env, asset: Address, config: ReserveConfig) -> u32 { + fn queue_init_reserve(e: Env, asset: Address, metadata: ReserveConfig) { storage::extend_instance(&e); let admin = storage::get_admin(&e); admin.require_auth(); - let index = pool::initialize_reserve(&e, &asset, &config); + storage::set_queued_reserve_init( + &e, + &QueuedReserveInit { + new_config: metadata.clone(), + unlock_block: e.ledger().sequence() + WEEK_IN_BLOCKS, + }, + &asset, + ); + + e.events().publish( + (Symbol::new(&e, "queue_init_reserve"), admin), + (asset, metadata), + ); + } + + fn cancel_init_reserve(e: Env, asset: Address) { + storage::extend_instance(&e); + let admin = storage::get_admin(&e); + admin.require_auth(); + + storage::del_queued_reserve_init(&e, &asset); e.events() - .publish((Symbol::new(&e, "init_reserve"), admin), (asset, index)); + .publish((Symbol::new(&e, "cancel_init_reserve"), admin), asset); + } + + fn init_reserve(e: Env, asset: Address) -> u32 { + let index = pool::execute_queued_reserve_initialization(&e, &asset); index } diff --git a/pool/src/errors.rs b/pool/src/errors.rs index a3d83b5f..51114bb2 100644 --- a/pool/src/errors.rs +++ b/pool/src/errors.rs @@ -12,6 +12,7 @@ pub enum PoolError { NegativeAmount = 4, InvalidPoolInitArgs = 5, InvalidReserveMetadata = 6, + InitNotUnlocked = 7, // Pool State Errors (10-19) InvalidHf = 10, InvalidPoolStatus = 11, diff --git a/pool/src/pool/config.rs b/pool/src/pool/config.rs index b12a3005..a4c4c703 100644 --- a/pool/src/pool/config.rs +++ b/pool/src/pool/config.rs @@ -2,7 +2,7 @@ use crate::{ errors::PoolError, storage::{self, PoolConfig, ReserveConfig, ReserveData}, }; -use soroban_sdk::{panic_with_error, Address, Env, Symbol}; +use soroban_sdk::{panic_with_error, Address, Env, Symbol, Vec}; use super::pool::Pool; @@ -19,6 +19,7 @@ pub fn execute_initialize( backstop_address: &Address, blnd_id: &Address, usdc_id: &Address, + reserves: &Vec<(Address, ReserveConfig)>, ) { if storage::has_admin(e) { panic_with_error!(e, PoolError::AlreadyInitialized); @@ -42,6 +43,9 @@ pub fn execute_initialize( ); storage::set_blnd_token(e, blnd_id); storage::set_usdc_token(e, usdc_id); + for (asset, config) in reserves.iter() { + initialize_reserve(e, &asset, &config); + } } /// Update the pool @@ -55,8 +59,25 @@ pub fn execute_update_pool(e: &Env, backstop_take_rate: u64) { storage::set_pool_config(e, &pool_config); } +/// Execute a queued reserve initialization for the pool +pub fn execute_queued_reserve_initialization(e: &Env, asset: &Address) -> u32 { + let queued_init = storage::get_queued_reserve_init(e, asset); + + if queued_init.unlock_block > e.ledger().sequence() { + panic_with_error!(e, PoolError::InitNotUnlocked); + } + + // remove queued init + storage::del_queued_reserve_init(e, asset); + + // initialize reserve + let index = initialize_reserve(e, asset, &queued_init.new_config); + + index +} + /// Initialize a reserve for the pool -pub fn initialize_reserve(e: &Env, asset: &Address, config: &ReserveConfig) -> u32 { +fn initialize_reserve(e: &Env, asset: &Address, config: &ReserveConfig) -> u32 { if storage::has_res(e, asset) { panic_with_error!(e, PoolError::AlreadyInitialized); } @@ -87,6 +108,8 @@ pub fn initialize_reserve(e: &Env, asset: &Address, config: &ReserveConfig) -> u backstop_credit: 0, }; storage::set_res_data(e, asset, &init_data); + e.events() + .publish((Symbol::new(&e, "init_reserve"),), (asset, index)); index } @@ -126,10 +149,12 @@ fn require_valid_reserve_metadata(e: &Env, metadata: &ReserveConfig) { #[cfg(test)] mod tests { + use crate::storage::QueuedReserveInit; use crate::testutils; use super::*; use soroban_sdk::testutils::{Address as _, Ledger, LedgerInfo}; + use soroban_sdk::vec; #[test] fn test_execute_initialize() { @@ -143,6 +168,21 @@ mod tests { let backstop_address = Address::generate(&e); let blnd_id = Address::generate(&e); let usdc_id = Address::generate(&e); + let (asset_id_0, _) = testutils::create_token_contract(&e, &admin); + let (asset_id_1, _) = testutils::create_token_contract(&e, &admin); + + let metadata = ReserveConfig { + index: 0, + decimals: 7, + c_factor: 0_7500000, + l_factor: 0_7500000, + util: 0_5000000, + max_util: 0_9500000, + r_one: 0_0500000, + r_two: 0_5000000, + r_three: 1_5000000, + reactivity: 100, + }; e.as_contract(&pool, || { execute_initialize( @@ -154,6 +194,11 @@ mod tests { &backstop_address, &blnd_id, &usdc_id, + &vec![ + &e, + (asset_id_0.clone(), metadata.clone()), + (asset_id_1.clone(), metadata.clone()), + ], ); assert_eq!(storage::get_admin(&e), admin); @@ -164,6 +209,19 @@ mod tests { 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); + let res_config_0 = storage::get_res_config(&e, &asset_id_0); + let res_config_1 = storage::get_res_config(&e, &asset_id_1); + assert_eq!(res_config_0.decimals, metadata.decimals); + assert_eq!(res_config_0.c_factor, metadata.c_factor); + assert_eq!(res_config_0.l_factor, metadata.l_factor); + assert_eq!(res_config_0.util, metadata.util); + assert_eq!(res_config_0.max_util, metadata.max_util); + assert_eq!(res_config_0.r_one, metadata.r_one); + assert_eq!(res_config_0.r_two, metadata.r_two); + assert_eq!(res_config_0.r_three, metadata.r_three); + assert_eq!(res_config_0.reactivity, metadata.reactivity); + assert_eq!(res_config_0.index, 0); + assert_eq!(res_config_1.index, 1); }); } @@ -206,7 +264,82 @@ mod tests { execute_update_pool(&e, 1_000_000_000u64); }); } + #[test] + fn test_execute_queued_initialize_reserve() { + let e = Env::default(); + let pool = testutils::create_pool(&e); + let bombadil = Address::generate(&e); + let (asset_id_0, _) = testutils::create_token_contract(&e, &bombadil); + + let metadata = ReserveConfig { + index: 0, + decimals: 7, + c_factor: 0_7500000, + l_factor: 0_7500000, + util: 0_5000000, + max_util: 0_9500000, + r_one: 0_0500000, + r_two: 0_5000000, + r_three: 1_5000000, + reactivity: 100, + }; + e.as_contract(&pool, || { + storage::set_queued_reserve_init( + &e, + &QueuedReserveInit { + new_config: metadata.clone(), + unlock_block: e.ledger().sequence(), + }, + &asset_id_0, + ); + execute_queued_reserve_initialization(&e, &asset_id_0); + let res_config_0 = storage::get_res_config(&e, &asset_id_0); + assert_eq!(res_config_0.decimals, metadata.decimals); + assert_eq!(res_config_0.c_factor, metadata.c_factor); + assert_eq!(res_config_0.l_factor, metadata.l_factor); + assert_eq!(res_config_0.util, metadata.util); + assert_eq!(res_config_0.max_util, metadata.max_util); + assert_eq!(res_config_0.r_one, metadata.r_one); + assert_eq!(res_config_0.r_two, metadata.r_two); + assert_eq!(res_config_0.r_three, metadata.r_three); + assert_eq!(res_config_0.reactivity, metadata.reactivity); + assert_eq!(res_config_0.index, 0); + }); + } + #[test] + #[should_panic(expected = "Error(Contract, #7)")] + fn test_execute_queued_initialize_reserve_requires_block_passed() { + let e = Env::default(); + let pool = testutils::create_pool(&e); + let bombadil = Address::generate(&e); + + let (asset_id_0, _) = testutils::create_token_contract(&e, &bombadil); + + let metadata = ReserveConfig { + index: 0, + decimals: 7, + c_factor: 0_7500000, + l_factor: 0_7500000, + util: 0_5000000, + max_util: 0_9500000, + r_one: 0_0500000, + r_two: 0_5000000, + r_three: 1_5000000, + reactivity: 100, + }; + e.as_contract(&pool, || { + storage::set_queued_reserve_init( + &e, + &QueuedReserveInit { + new_config: metadata.clone(), + unlock_block: e.ledger().sequence() + 1, + }, + &asset_id_0, + ); + execute_queued_reserve_initialization(&e, &asset_id_0); + }); + } #[test] fn test_initialize_reserve() { let e = Env::default(); diff --git a/pool/src/pool/mod.rs b/pool/src/pool/mod.rs index d501510a..f659c763 100644 --- a/pool/src/pool/mod.rs +++ b/pool/src/pool/mod.rs @@ -6,7 +6,8 @@ pub use bad_debt::{burn_backstop_bad_debt, transfer_bad_debt_to_backstop}; mod config; pub use config::{ - execute_initialize, execute_update_pool, execute_update_reserve, initialize_reserve, + execute_initialize, execute_queued_reserve_initialization, execute_update_pool, + execute_update_reserve, }; mod health_factor; diff --git a/pool/src/storage.rs b/pool/src/storage.rs index bf4e8ac1..45230a30 100644 --- a/pool/src/storage.rs +++ b/pool/src/storage.rs @@ -45,6 +45,12 @@ pub struct ReserveConfig { pub r_three: u32, // the R3 value in the interest rate formula scaled expressed in 7 decimals pub reactivity: u32, // the reactivity constant for the reserve scaled expressed in 9 decimals } +#[derive(Clone)] +#[contracttype] +pub struct QueuedReserveInit { + pub new_config: ReserveConfig, + pub unlock_block: u32, +} /// The data for a reserve asset #[derive(Clone)] @@ -116,6 +122,8 @@ pub struct AuctionKey { pub enum PoolDataKey { // A map of underlying asset's contract address to reserve config ResConfig(Address), + // A map of underlying asset's contract address to queued reserve init + ResInit(Address), // A map of underlying asset's contract address to reserve data ResData(Address), // The reserve's emission config @@ -359,6 +367,51 @@ pub fn has_res(e: &Env, asset: &Address) -> bool { e.storage().persistent().has(&key) } +/// Fetch a queued reserve initialization +/// +/// ### Arguments +/// * `asset` - The contract address of the asset +/// +/// ### Panics +/// If the reserve initialization has not been queued +pub fn get_queued_reserve_init(e: &Env, asset: &Address) -> QueuedReserveInit { + let key = PoolDataKey::ResInit(asset.clone()); + e.storage() + .persistent() + .extend_ttl(&key, LEDGER_THRESHOLD_SHARED, LEDGER_BUMP_SHARED); + e.storage() + .persistent() + .get::(&key) + .unwrap_optimized() +} + +/// Set a new swap in the queue +/// +/// ### Arguments +/// * `asset` - The contract address of the asset +/// * `config` - The reserve configuration for the asset +pub fn set_queued_reserve_init(e: &Env, res_init: &QueuedReserveInit, asset: &Address) { + let key = PoolDataKey::ResInit(asset.clone()); + e.storage() + .persistent() + .set::(&key, res_init); + e.storage() + .persistent() + .extend_ttl(&key, LEDGER_THRESHOLD_SHARED, LEDGER_BUMP_SHARED); +} + +/// Delete a queued reserve initialization +/// +/// ### Arguments +/// * `asset` - The contract address of the asset +/// +/// ### Panics +/// If the reserve initialization has not been queued +pub fn del_queued_reserve_init(e: &Env, asset: &Address) { + let key = PoolDataKey::ResInit(asset.clone()); + e.storage().persistent().remove(&key); +} + /********** Reserve Data (ResData) **********/ /// Fetch the reserve data for an asset diff --git a/test-suites/src/test_fixture.rs b/test-suites/src/test_fixture.rs index f53fef33..df62cc47 100644 --- a/test-suites/src/test_fixture.rs +++ b/test-suites/src/test_fixture.rs @@ -5,7 +5,7 @@ use crate::backstop::create_backstop; use crate::emitter::create_emitter; use crate::liquidity_pool::{create_lp_pool, LPClient}; use crate::oracle::create_mock_oracle; -use crate::pool::POOL_WASM; +use crate::pool::{default_reserve_metadata, POOL_WASM}; use crate::pool_factory::create_pool_factory; use crate::token::{create_stellar_token, create_token}; use backstop::BackstopClient; @@ -174,6 +174,7 @@ impl TestFixture<'_> { &BytesN::<32>::random(&self.env), &self.oracle.address, &backstop_take_rate, + &svec![&self.env,], ); self.pools.push(PoolFixture { pool: PoolClient::new(&self.env, &pool_id), @@ -189,9 +190,7 @@ impl TestFixture<'_> { ) { let mut pool_fixture = self.pools.remove(pool_index); let token = &self.tokens[asset_index]; - let index = pool_fixture - .pool - .init_reserve(&token.address, &reserve_config); + let index = pool_fixture.pool.init_reserve(&token.address); pool_fixture.reserves.insert(asset_index, index); self.pools.insert(pool_index, pool_fixture); } diff --git a/test-suites/tests/test_pool.rs b/test-suites/tests/test_pool.rs index 98a9b382..195bc0b0 100644 --- a/test-suites/tests/test_pool.rs +++ b/test-suites/tests/test_pool.rs @@ -548,6 +548,7 @@ fn test_pool_config() { &Address::generate(&fixture.env), &Address::generate(&fixture.env), &Address::generate(&fixture.env), + &vec![&fixture.env], ); assert!(result.is_err()); @@ -591,9 +592,7 @@ fn test_pool_config() { let mut reserve_config = default_reserve_metadata(); reserve_config.l_factor = 0_500_0000; reserve_config.c_factor = 0_200_0000; - pool_fixture - .pool - .init_reserve(&blnd.address, &reserve_config); + pool_fixture.pool.init_reserve(&blnd.address); assert_eq!( fixture.env.auths()[0], ( From 65390298e6cbd39e01097967a23950cd3ef1172d Mon Sep 17 00:00:00 2001 From: markuspluna <59978114+markuspluna@users.noreply.github.com> Date: Thu, 14 Dec 2023 16:48:31 -0800 Subject: [PATCH 2/8] Pool: abstracted reserve init queuing --- pool/src/contract.rs | 16 ++----- pool/src/pool/config.rs | 103 +++++++++++++++++++++++++++++++++++++--- pool/src/pool/mod.rs | 3 +- 3 files changed, 103 insertions(+), 19 deletions(-) diff --git a/pool/src/contract.rs b/pool/src/contract.rs index a9d74bf8..d275f0b2 100644 --- a/pool/src/contract.rs +++ b/pool/src/contract.rs @@ -1,9 +1,8 @@ use crate::{ auctions::{self, AuctionData}, - constants::WEEK_IN_BLOCKS, emissions::{self, ReserveEmissionMetadata}, pool::{self, Positions, Request}, - storage::{self, QueuedReserveInit, ReserveConfig}, + storage::{self, ReserveConfig}, }; use soroban_sdk::{contract, contractclient, contractimpl, Address, Env, Symbol, Vec}; @@ -290,14 +289,7 @@ impl Pool for PoolContract { let admin = storage::get_admin(&e); admin.require_auth(); - storage::set_queued_reserve_init( - &e, - &QueuedReserveInit { - new_config: metadata.clone(), - unlock_block: e.ledger().sequence() + WEEK_IN_BLOCKS, - }, - &asset, - ); + pool::execute_queue_reserve_initialization(&e, &asset, &metadata); e.events().publish( (Symbol::new(&e, "queue_init_reserve"), admin), @@ -310,14 +302,14 @@ impl Pool for PoolContract { let admin = storage::get_admin(&e); admin.require_auth(); - storage::del_queued_reserve_init(&e, &asset); + pool::execute_cancel_queued_reserve_initialization(&e, &asset); e.events() .publish((Symbol::new(&e, "cancel_init_reserve"), admin), asset); } fn init_reserve(e: Env, asset: Address) -> u32 { - let index = pool::execute_queued_reserve_initialization(&e, &asset); + let index = pool::execute_initialize_queued_reserve(&e, &asset); index } diff --git a/pool/src/pool/config.rs b/pool/src/pool/config.rs index a4c4c703..fe17fa29 100644 --- a/pool/src/pool/config.rs +++ b/pool/src/pool/config.rs @@ -1,6 +1,7 @@ use crate::{ + constants::WEEK_IN_BLOCKS, errors::PoolError, - storage::{self, PoolConfig, ReserveConfig, ReserveData}, + storage::{self, PoolConfig, QueuedReserveInit, ReserveConfig, ReserveData}, }; use soroban_sdk::{panic_with_error, Address, Env, Symbol, Vec}; @@ -59,8 +60,25 @@ pub fn execute_update_pool(e: &Env, backstop_take_rate: u64) { storage::set_pool_config(e, &pool_config); } +/// Execute a queueing a reserve initialization for the pool +pub fn execute_queue_reserve_initialization(e: &Env, asset: &Address, metadata: &ReserveConfig) { + storage::set_queued_reserve_init( + &e, + &QueuedReserveInit { + new_config: metadata.clone(), + unlock_block: e.ledger().sequence() + WEEK_IN_BLOCKS, + }, + &asset, + ); +} + +/// Execute cancelling a queueing a reserve initialization for the pool +pub fn execute_cancel_queued_reserve_initialization(e: &Env, asset: &Address) { + storage::del_queued_reserve_init(&e, &asset); +} + /// Execute a queued reserve initialization for the pool -pub fn execute_queued_reserve_initialization(e: &Env, asset: &Address) -> u32 { +pub fn execute_initialize_queued_reserve(e: &Env, asset: &Address) -> u32 { let queued_init = storage::get_queued_reserve_init(e, asset); if queued_init.unlock_block > e.ledger().sequence() { @@ -265,7 +283,47 @@ mod tests { }); } #[test] - fn test_execute_queued_initialize_reserve() { + fn test_execute_queue_reserve_initialization() { + let e = Env::default(); + let pool = testutils::create_pool(&e); + let bombadil = Address::generate(&e); + + let (asset_id_0, _) = testutils::create_token_contract(&e, &bombadil); + + let metadata = ReserveConfig { + index: 0, + decimals: 7, + c_factor: 0_7500000, + l_factor: 0_7500000, + util: 0_5000000, + max_util: 0_9500000, + r_one: 0_0500000, + r_two: 0_5000000, + r_three: 1_5000000, + reactivity: 100, + }; + e.as_contract(&pool, || { + execute_queue_reserve_initialization(&e, &asset_id_0, &metadata); + let queued_init = storage::get_queued_reserve_init(&e, &asset_id_0); + assert_eq!(queued_init.new_config.decimals, metadata.decimals); + assert_eq!(queued_init.new_config.c_factor, metadata.c_factor); + assert_eq!(queued_init.new_config.l_factor, metadata.l_factor); + assert_eq!(queued_init.new_config.util, metadata.util); + assert_eq!(queued_init.new_config.max_util, metadata.max_util); + assert_eq!(queued_init.new_config.r_one, metadata.r_one); + assert_eq!(queued_init.new_config.r_two, metadata.r_two); + assert_eq!(queued_init.new_config.r_three, metadata.r_three); + assert_eq!(queued_init.new_config.reactivity, metadata.reactivity); + assert_eq!(queued_init.new_config.index, 0); + assert_eq!( + queued_init.unlock_block, + e.ledger().sequence() + WEEK_IN_BLOCKS + ); + }); + } + #[test] + #[should_panic(expected = "Error(Storage, MissingValue)")] + fn test_execute_cancel_queued_reserve_initialization() { let e = Env::default(); let pool = testutils::create_pool(&e); let bombadil = Address::generate(&e); @@ -293,8 +351,41 @@ mod tests { }, &asset_id_0, ); - execute_queued_reserve_initialization(&e, &asset_id_0); - let res_config_0 = storage::get_res_config(&e, &asset_id_0); + execute_cancel_queued_reserve_initialization(&e, &asset_id_0); + storage::get_queued_reserve_init(&e, &asset_id_0); + }); + } + #[test] + fn test_execute_initialize_queued_reserve() { + let e = Env::default(); + let pool = testutils::create_pool(&e); + let bombadil = Address::generate(&e); + + let (asset_id_0, _) = testutils::create_token_contract(&e, &bombadil); + + let metadata = ReserveConfig { + index: 0, + decimals: 7, + c_factor: 0_7500000, + l_factor: 0_7500000, + util: 0_5000000, + max_util: 0_9500000, + r_one: 0_0500000, + r_two: 0_5000000, + r_three: 1_5000000, + reactivity: 100, + }; + e.as_contract(&pool, || { + storage::set_queued_reserve_init( + &e, + &QueuedReserveInit { + new_config: metadata.clone(), + unlock_block: e.ledger().sequence(), + }, + &asset_id_0, + ); + execute_initialize_queued_reserve(&e, &asset_id_0); + let res_config_0: ReserveConfig = storage::get_res_config(&e, &asset_id_0); assert_eq!(res_config_0.decimals, metadata.decimals); assert_eq!(res_config_0.c_factor, metadata.c_factor); assert_eq!(res_config_0.l_factor, metadata.l_factor); @@ -337,7 +428,7 @@ mod tests { }, &asset_id_0, ); - execute_queued_reserve_initialization(&e, &asset_id_0); + execute_initialize_queued_reserve(&e, &asset_id_0); }); } #[test] diff --git a/pool/src/pool/mod.rs b/pool/src/pool/mod.rs index f659c763..7decbe71 100644 --- a/pool/src/pool/mod.rs +++ b/pool/src/pool/mod.rs @@ -6,7 +6,8 @@ pub use bad_debt::{burn_backstop_bad_debt, transfer_bad_debt_to_backstop}; mod config; pub use config::{ - execute_initialize, execute_queued_reserve_initialization, execute_update_pool, + execute_cancel_queued_reserve_initialization, execute_initialize, + execute_initialize_queued_reserve, execute_queue_reserve_initialization, execute_update_pool, execute_update_reserve, }; From a2731113dcf5e56d652b3772af199994176335e8 Mon Sep 17 00:00:00 2001 From: markuspluna <59978114+markuspluna@users.noreply.github.com> Date: Fri, 15 Dec 2023 11:59:20 -0800 Subject: [PATCH 3/8] updated tests --- pool-factory/src/pool_factory.rs | 5 +- pool-factory/src/storage.rs | 18 ----- pool-factory/src/test.rs | 55 ++------------ pool/src/constants.rs | 2 +- pool/src/contract.rs | 22 +++++- pool/src/pool/config.rs | 125 +++++++++++++++++++++++-------- pool/src/pool/mod.rs | 4 +- pool/src/storage.rs | 4 +- test-suites/src/setup.rs | 76 +++++++++++-------- test-suites/src/test_fixture.rs | 27 +++++-- test-suites/tests/test_pool.rs | 16 ++-- 11 files changed, 200 insertions(+), 154 deletions(-) diff --git a/pool-factory/src/pool_factory.rs b/pool-factory/src/pool_factory.rs index b97daa3e..f8fbdea8 100644 --- a/pool-factory/src/pool_factory.rs +++ b/pool-factory/src/pool_factory.rs @@ -1,6 +1,6 @@ use crate::{ errors::PoolFactoryError, - storage::{self, PoolInitMeta, ReserveConfig}, + storage::{self, PoolInitMeta}, }; use soroban_sdk::{ contract, contractclient, contractimpl, panic_with_error, vec, Address, BytesN, Env, IntoVal, @@ -32,7 +32,6 @@ pub trait PoolFactory { salt: BytesN<32>, oracle: Address, backstop_take_rate: u64, - reserves: Vec<(Address, ReserveConfig)>, ) -> Address; /// Checks if contract address was deployed by the factory @@ -61,7 +60,6 @@ impl PoolFactory for PoolFactoryContract { salt: BytesN<32>, oracle: Address, backstop_take_rate: u64, - reserves: Vec<(Address, ReserveConfig)>, ) -> Address { admin.require_auth(); storage::extend_instance(&e); @@ -80,7 +78,6 @@ impl PoolFactory for PoolFactoryContract { init_args.push_back(pool_init_meta.backstop.to_val()); init_args.push_back(pool_init_meta.blnd_id.to_val()); init_args.push_back(pool_init_meta.usdc_id.to_val()); - init_args.push_back(reserves.to_val()); let pool_address = e .deployer() .with_current_contract(salt) diff --git a/pool-factory/src/storage.rs b/pool-factory/src/storage.rs index 00dceb0d..ad726e96 100644 --- a/pool-factory/src/storage.rs +++ b/pool-factory/src/storage.rs @@ -4,24 +4,6 @@ use soroban_sdk::{contracttype, unwrap::UnwrapOptimized, Address, BytesN, Env, S pub(crate) const LEDGER_THRESHOLD: u32 = 518400; // TODO: Check on phase 1 max ledger entry bump pub(crate) const LEDGER_BUMP: u32 = 535670; // TODO: Check on phase 1 max ledger entry bump -/// The configuration information about a reserve asset -/// TODO: Duplicated from pool/storage.rs. Can this be moved to a common location? -#[derive(Clone)] -#[contracttype] -pub struct ReserveConfig { - pub index: u32, // the index of the reserve in the list - pub decimals: u32, // the decimals used in both the bToken and underlying contract - pub c_factor: u32, // the collateral factor for the reserve scaled expressed in 7 decimals - pub l_factor: u32, // the liability factor for the reserve scaled expressed in 7 decimals - pub util: u32, // the target utilization rate scaled expressed in 7 decimals - pub max_util: u32, // the maximum allowed utilization rate scaled expressed in 7 decimals - pub r_one: u32, // the R1 value in the interest rate formula scaled expressed in 7 decimals - pub r_two: u32, // the R2 value in the interest rate formula scaled expressed in 7 decimals - pub r_three: u32, // the R3 value in the interest rate formula scaled expressed in 7 decimals - pub reactivity: u32, // the reactivity constant for the reserve scaled expressed in 9 decimals -} - - #[derive(Clone)] #[contracttype] pub enum PoolFactoryDataKey { diff --git a/pool-factory/src/test.rs b/pool-factory/src/test.rs index 917dd18e..50bed872 100644 --- a/pool-factory/src/test.rs +++ b/pool-factory/src/test.rs @@ -5,10 +5,7 @@ use soroban_sdk::{ vec, Address, BytesN, Env, IntoVal, Symbol, }; -use crate::{ - storage::ReserveConfig, test::pool::PoolDataKey, PoolFactoryClient, PoolFactoryContract, - PoolInitMeta, -}; +use crate::{PoolFactoryClient, PoolFactoryContract, PoolInitMeta}; mod pool { soroban_sdk::contractimport!(file = "../target/wasm32-unknown-unknown/optimized/pool.wasm"); @@ -51,27 +48,9 @@ fn test_pool_factory() { let name1 = Symbol::new(&e, "pool1"); let name2 = Symbol::new(&e, "pool2"); let salt = BytesN::<32>::random(&e); - let asset_id_0 = Address::generate(&e); - let metadata = ReserveConfig { - index: 0, - decimals: 7, - c_factor: 0_7500000, - l_factor: 0_7500000, - util: 0_5000000, - max_util: 0_9500000, - r_one: 0_0500000, - r_two: 0_5000000, - r_three: 1_5000000, - reactivity: 100, - }; - let deployed_pool_address_1 = pool_factory_client.deploy( - &bombadil, - &name1, - &salt, - &oracle, - &backstop_rate, - &vec![&e, (asset_id_0.clone(), metadata.clone())], - ); + + let deployed_pool_address_1 = + pool_factory_client.deploy(&bombadil, &name1, &salt, &oracle, &backstop_rate); let event = vec![&e, e.events().all().last_unchecked()]; assert_eq!( @@ -87,14 +66,8 @@ fn test_pool_factory() { ); let salt = BytesN::<32>::random(&e); - let deployed_pool_address_2 = pool_factory_client.deploy( - &bombadil, - &name2, - &salt, - &oracle, - &backstop_rate, - &vec![&e, (asset_id_0.clone(), metadata.clone())], - ); + let deployed_pool_address_2 = + pool_factory_client.deploy(&bombadil, &name2, &salt, &oracle, &backstop_rate); e.as_contract(&deployed_pool_address_1, || { assert_eq!( @@ -136,22 +109,6 @@ fn test_pool_factory() { .unwrap(), usdc_id.clone() ); - let key = PoolDataKey::ResConfig(asset_id_0.clone()); - let set_config = e - .storage() - .persistent() - .get::<_, ReserveConfig>(&key) - .unwrap(); - assert_eq!(set_config.decimals, metadata.decimals); - assert_eq!(set_config.c_factor, metadata.c_factor); - assert_eq!(set_config.l_factor, metadata.l_factor); - assert_eq!(set_config.util, metadata.util); - assert_eq!(set_config.max_util, metadata.max_util); - assert_eq!(set_config.r_one, metadata.r_one); - assert_eq!(set_config.r_two, metadata.r_two); - assert_eq!(set_config.r_three, metadata.r_three); - assert_eq!(set_config.reactivity, metadata.reactivity); - assert_eq!(set_config.index, 0); }); assert_ne!(deployed_pool_address_1, deployed_pool_address_2); assert!(pool_factory_client.is_pool(&deployed_pool_address_1)); diff --git a/pool/src/constants.rs b/pool/src/constants.rs index 2d415947..95ee29f3 100644 --- a/pool/src/constants.rs +++ b/pool/src/constants.rs @@ -10,4 +10,4 @@ pub const SCALAR_7: i128 = 1_0000000; pub const SECONDS_PER_YEAR: i128 = 31536000; // approximate week in blocks assuming 5 seconds per block -pub const WEEK_IN_BLOCKS: u32 = 120960; +pub const SECONDS_PER_WEEK: u64 = 604800; diff --git a/pool/src/contract.rs b/pool/src/contract.rs index d275f0b2..a650b9c0 100644 --- a/pool/src/contract.rs +++ b/pool/src/contract.rs @@ -37,7 +37,6 @@ pub trait Pool { backstop_id: Address, blnd_id: Address, usdc_id: Address, - reserves: Vec<(Address, ReserveConfig)>, ); /// (Admin only) Set a new address as the admin of this pool @@ -58,6 +57,17 @@ pub trait Pool { /// If the caller is not the admin fn update_pool(e: Env, backstop_take_rate: u64); + /// (Admin only) Initializes the first pool reserves + /// + /// ### Arguments + /// * `reserves` - A vector of tuples of the form `(asset, config) + /// * `asset` - The underlying asset to add as a reserve + /// * `config` - The ReserveConfig for the reserve + /// + /// ### Panics + /// If the caller is not the admin + fn init_initial_reserves(e: Env, reserves: Vec<(Address, ReserveConfig)>); + /// (Admin only) Queues the initialization of a reserve in the pool /// /// ### Arguments @@ -245,7 +255,6 @@ impl Pool for PoolContract { backstop_id: Address, blnd_id: Address, usdc_id: Address, - reserves: Vec<(Address, ReserveConfig)>, ) { storage::extend_instance(&e); @@ -258,7 +267,6 @@ impl Pool for PoolContract { &backstop_id, &blnd_id, &usdc_id, - &reserves, ); } @@ -284,6 +292,14 @@ impl Pool for PoolContract { .publish((Symbol::new(&e, "update_pool"), admin), backstop_take_rate); } + fn init_initial_reserves(e: Env, reserves: Vec<(Address, ReserveConfig)>) { + storage::extend_instance(&e); + let admin = storage::get_admin(&e); + admin.require_auth(); + + pool::execute_initialize_initial_reserves(&e, &reserves); + } + fn queue_init_reserve(e: Env, asset: Address, metadata: ReserveConfig) { storage::extend_instance(&e); let admin = storage::get_admin(&e); diff --git a/pool/src/pool/config.rs b/pool/src/pool/config.rs index fe17fa29..c9a0e477 100644 --- a/pool/src/pool/config.rs +++ b/pool/src/pool/config.rs @@ -1,5 +1,5 @@ use crate::{ - constants::WEEK_IN_BLOCKS, + constants::SECONDS_PER_WEEK, errors::PoolError, storage::{self, PoolConfig, QueuedReserveInit, ReserveConfig, ReserveData}, }; @@ -20,7 +20,6 @@ pub fn execute_initialize( backstop_address: &Address, blnd_id: &Address, usdc_id: &Address, - reserves: &Vec<(Address, ReserveConfig)>, ) { if storage::has_admin(e) { panic_with_error!(e, PoolError::AlreadyInitialized); @@ -44,9 +43,6 @@ pub fn execute_initialize( ); storage::set_blnd_token(e, blnd_id); storage::set_usdc_token(e, usdc_id); - for (asset, config) in reserves.iter() { - initialize_reserve(e, &asset, &config); - } } /// Update the pool @@ -60,13 +56,25 @@ pub fn execute_update_pool(e: &Env, backstop_take_rate: u64) { storage::set_pool_config(e, &pool_config); } +/// Execute initial reserve initialization +pub fn execute_initialize_initial_reserves(e: &Env, reserves: &Vec<(Address, ReserveConfig)>) { + // confirm the pool has no reserves + if storage::get_res_list(e).len() > 0 { + panic_with_error!(e, PoolError::AlreadyInitialized); + } + // initialize reserves + for (asset, config) in reserves.iter() { + initialize_reserve(e, &asset, &config); + } +} + /// Execute a queueing a reserve initialization for the pool pub fn execute_queue_reserve_initialization(e: &Env, asset: &Address, metadata: &ReserveConfig) { storage::set_queued_reserve_init( &e, &QueuedReserveInit { new_config: metadata.clone(), - unlock_block: e.ledger().sequence() + WEEK_IN_BLOCKS, + unlock_time: e.ledger().timestamp() + SECONDS_PER_WEEK, }, &asset, ); @@ -81,7 +89,7 @@ pub fn execute_cancel_queued_reserve_initialization(e: &Env, asset: &Address) { pub fn execute_initialize_queued_reserve(e: &Env, asset: &Address) -> u32 { let queued_init = storage::get_queued_reserve_init(e, asset); - if queued_init.unlock_block > e.ledger().sequence() { + if queued_init.unlock_time > e.ledger().timestamp() { panic_with_error!(e, PoolError::InitNotUnlocked); } @@ -212,11 +220,6 @@ mod tests { &backstop_address, &blnd_id, &usdc_id, - &vec![ - &e, - (asset_id_0.clone(), metadata.clone()), - (asset_id_1.clone(), metadata.clone()), - ], ); assert_eq!(storage::get_admin(&e), admin); @@ -227,19 +230,6 @@ mod tests { 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); - let res_config_0 = storage::get_res_config(&e, &asset_id_0); - let res_config_1 = storage::get_res_config(&e, &asset_id_1); - assert_eq!(res_config_0.decimals, metadata.decimals); - assert_eq!(res_config_0.c_factor, metadata.c_factor); - assert_eq!(res_config_0.l_factor, metadata.l_factor); - assert_eq!(res_config_0.util, metadata.util); - assert_eq!(res_config_0.max_util, metadata.max_util); - assert_eq!(res_config_0.r_one, metadata.r_one); - assert_eq!(res_config_0.r_two, metadata.r_two); - assert_eq!(res_config_0.r_three, metadata.r_three); - assert_eq!(res_config_0.reactivity, metadata.reactivity); - assert_eq!(res_config_0.index, 0); - assert_eq!(res_config_1.index, 1); }); } @@ -283,6 +273,81 @@ mod tests { }); } #[test] + fn test_initialize_initial_reserves() { + let e = Env::default(); + let pool = testutils::create_pool(&e); + let bombadil = Address::generate(&e); + + let (asset_id_0, _) = testutils::create_token_contract(&e, &bombadil); + let (asset_id_1, _) = testutils::create_token_contract(&e, &bombadil); + + let metadata = ReserveConfig { + index: 0, + decimals: 7, + c_factor: 0_7500000, + l_factor: 0_7500000, + util: 0_5000000, + max_util: 0_9500000, + r_one: 0_0500000, + r_two: 0_5000000, + r_three: 1_5000000, + reactivity: 100, + }; + e.as_contract(&pool, || { + execute_initialize_initial_reserves( + &e, + &vec![ + &e, + (asset_id_0.clone(), metadata.clone()), + (asset_id_1.clone(), metadata.clone()), + ], + ); + let res_config_0 = storage::get_res_config(&e, &asset_id_0); + let res_config_1 = storage::get_res_config(&e, &asset_id_1); + assert_eq!(res_config_0.decimals, metadata.decimals); + assert_eq!(res_config_0.c_factor, metadata.c_factor); + assert_eq!(res_config_0.l_factor, metadata.l_factor); + assert_eq!(res_config_0.util, metadata.util); + assert_eq!(res_config_0.max_util, metadata.max_util); + assert_eq!(res_config_0.r_one, metadata.r_one); + assert_eq!(res_config_0.r_two, metadata.r_two); + assert_eq!(res_config_0.r_three, metadata.r_three); + assert_eq!(res_config_0.reactivity, metadata.reactivity); + assert_eq!(res_config_0.index, 0); + assert_eq!(res_config_1.index, 1); + }); + } + #[test] + #[should_panic(expected = "Error(Contract, #3)")] + fn test_initialize_initial_reserves_panics_if_reserves_exist() { + let e = Env::default(); + let pool = testutils::create_pool(&e); + let bombadil = Address::generate(&e); + + let (asset_id_0, _) = testutils::create_token_contract(&e, &bombadil); + let (asset_id_1, _) = testutils::create_token_contract(&e, &bombadil); + + let metadata = ReserveConfig { + index: 0, + decimals: 7, + c_factor: 0_7500000, + l_factor: 0_7500000, + util: 0_5000000, + max_util: 0_9500000, + r_one: 0_0500000, + r_two: 0_5000000, + r_three: 1_5000000, + reactivity: 100, + }; + e.as_contract(&pool, || { + initialize_reserve(&e, &asset_id_0, &metadata); + execute_initialize_initial_reserves( + &e, + &vec![&e, (asset_id_1.clone(), metadata.clone())], + ); + }); + } + #[test] fn test_execute_queue_reserve_initialization() { let e = Env::default(); let pool = testutils::create_pool(&e); @@ -316,8 +381,8 @@ mod tests { assert_eq!(queued_init.new_config.reactivity, metadata.reactivity); assert_eq!(queued_init.new_config.index, 0); assert_eq!( - queued_init.unlock_block, - e.ledger().sequence() + WEEK_IN_BLOCKS + queued_init.unlock_time, + e.ledger().timestamp() + SECONDS_PER_WEEK ); }); } @@ -347,7 +412,7 @@ mod tests { &e, &QueuedReserveInit { new_config: metadata.clone(), - unlock_block: e.ledger().sequence(), + unlock_time: e.ledger().timestamp(), }, &asset_id_0, ); @@ -380,7 +445,7 @@ mod tests { &e, &QueuedReserveInit { new_config: metadata.clone(), - unlock_block: e.ledger().sequence(), + unlock_time: e.ledger().timestamp(), }, &asset_id_0, ); @@ -424,7 +489,7 @@ mod tests { &e, &QueuedReserveInit { new_config: metadata.clone(), - unlock_block: e.ledger().sequence() + 1, + unlock_time: e.ledger().timestamp() + 1, }, &asset_id_0, ); diff --git a/pool/src/pool/mod.rs b/pool/src/pool/mod.rs index 7decbe71..e27a6b12 100644 --- a/pool/src/pool/mod.rs +++ b/pool/src/pool/mod.rs @@ -7,8 +7,8 @@ pub use bad_debt::{burn_backstop_bad_debt, transfer_bad_debt_to_backstop}; mod config; pub use config::{ execute_cancel_queued_reserve_initialization, execute_initialize, - execute_initialize_queued_reserve, execute_queue_reserve_initialization, execute_update_pool, - execute_update_reserve, + execute_initialize_initial_reserves, execute_initialize_queued_reserve, + execute_queue_reserve_initialization, execute_update_pool, execute_update_reserve, }; mod health_factor; diff --git a/pool/src/storage.rs b/pool/src/storage.rs index 45230a30..c4225163 100644 --- a/pool/src/storage.rs +++ b/pool/src/storage.rs @@ -49,7 +49,7 @@ pub struct ReserveConfig { #[contracttype] pub struct QueuedReserveInit { pub new_config: ReserveConfig, - pub unlock_block: u32, + pub unlock_time: u64, } /// The data for a reserve asset @@ -385,7 +385,7 @@ pub fn get_queued_reserve_init(e: &Env, asset: &Address) -> QueuedReserveInit { .unwrap_optimized() } -/// Set a new swap in the queue +/// Set a new queued reserve initialization /// /// ### Arguments /// * `asset` - The contract address of the asset diff --git a/test-suites/src/setup.rs b/test-suites/src/setup.rs index 9c012c06..d213d654 100644 --- a/test-suites/src/setup.rs +++ b/test-suites/src/setup.rs @@ -1,5 +1,5 @@ use pool::{Request, ReserveEmissionMetadata}; -use soroban_sdk::{testutils::Address as _, vec, Address, Symbol, Vec}; +use soroban_sdk::{testutils::Address as _, vec as svec, Address, Symbol, Vec as SVec}; use crate::{ pool::default_reserve_metadata, @@ -10,28 +10,64 @@ use crate::{ pub fn create_fixture_with_data<'a>(wasm: bool) -> TestFixture<'a> { let mut fixture = TestFixture::create(wasm); + // mint whale tokens + let frodo = Address::generate(&fixture.env); + fixture.users.push(frodo.clone()); + fixture.tokens[TokenIndex::STABLE].mint(&frodo, &(100_000 * 10i128.pow(6))); + fixture.tokens[TokenIndex::XLM].mint(&frodo, &(1_000_000 * SCALAR_7)); + fixture.tokens[TokenIndex::WETH].mint(&frodo, &(100 * 10i128.pow(9))); + + // mint LP tokens with whale + fixture.tokens[TokenIndex::BLND].mint(&frodo, &(500_0010_000_0000_0000 * SCALAR_7)); + // fixture.tokens[TokenIndex::BLND].approve(&frodo, &fixture.lp.address, &i128::MAX, &99999); + fixture.tokens[TokenIndex::USDC].mint(&frodo, &(12_5010_000_0000_0000 * SCALAR_7)); + // fixture.tokens[TokenIndex::USDC].approve(&frodo, &fixture.lp.address, &i128::MAX, &99999); + fixture.lp.join_pool( + &(500_000_0000 * SCALAR_7), + &svec![ + &fixture.env, + 500_0010_000_0000_0000 * SCALAR_7, + 12_5010_000_0000_0000 * SCALAR_7, + ], + &frodo, + ); + // create pool - fixture.create_pool(Symbol::new(&fixture.env, "Teapot"), 0_100_000_000); let mut stable_config = default_reserve_metadata(); stable_config.decimals = 6; stable_config.c_factor = 0_900_0000; stable_config.l_factor = 0_950_0000; stable_config.util = 0_850_0000; - fixture.create_pool_reserve(0, TokenIndex::STABLE, stable_config); let mut xlm_config = default_reserve_metadata(); xlm_config.c_factor = 0_750_0000; xlm_config.l_factor = 0_750_0000; xlm_config.util = 0_500_0000; - fixture.create_pool_reserve(0, TokenIndex::XLM, xlm_config); let mut weth_config = default_reserve_metadata(); weth_config.decimals = 9; weth_config.c_factor = 0_800_0000; weth_config.l_factor = 0_800_0000; weth_config.util = 0_700_0000; - fixture.create_pool_reserve(0, TokenIndex::WETH, weth_config); + + fixture.create_pool( + Symbol::new(&fixture.env, "Teapot"), + 0_100_000_000, + svec![ + &fixture.env, + ( + fixture.tokens[TokenIndex::STABLE].address.clone(), + stable_config + ), + (fixture.tokens[TokenIndex::XLM].address.clone(), xlm_config), + ( + fixture.tokens[TokenIndex::WETH].address.clone(), + weth_config + ), + ], + vec![TokenIndex::STABLE, TokenIndex::XLM, TokenIndex::WETH], + ); // enable emissions for pool let pool_fixture = &fixture.pools[0]; @@ -51,28 +87,6 @@ pub fn create_fixture_with_data<'a>(wasm: bool) -> TestFixture<'a> { ]; pool_fixture.pool.set_emissions_config(&reserve_emissions); - // mint whale tokens - let frodo = Address::generate(&fixture.env); - fixture.users.push(frodo.clone()); - fixture.tokens[TokenIndex::STABLE].mint(&frodo, &(100_000 * 10i128.pow(6))); - fixture.tokens[TokenIndex::XLM].mint(&frodo, &(1_000_000 * SCALAR_7)); - fixture.tokens[TokenIndex::WETH].mint(&frodo, &(100 * 10i128.pow(9))); - - // mint LP tokens with whale - fixture.tokens[TokenIndex::BLND].mint(&frodo, &(500_0010_000_0000_0000 * SCALAR_7)); - // fixture.tokens[TokenIndex::BLND].approve(&frodo, &fixture.lp.address, &i128::MAX, &99999); - fixture.tokens[TokenIndex::USDC].mint(&frodo, &(12_5010_000_0000_0000 * SCALAR_7)); - // fixture.tokens[TokenIndex::USDC].approve(&frodo, &fixture.lp.address, &i128::MAX, &99999); - fixture.lp.join_pool( - &(500_000_0000 * SCALAR_7), - &vec![ - &fixture.env, - 500_0010_000_0000_0000 * SCALAR_7, - 12_5010_000_0000_0000 * SCALAR_7, - ], - &frodo, - ); - // deposit into backstop, add to reward zone fixture .backstop @@ -105,7 +119,7 @@ pub fn create_fixture_with_data<'a>(wasm: bool) -> TestFixture<'a> { // fixture.tokens[TokenIndex::XLM].approve(&frodo, &pool_fixture.pool.address, &i128::MAX, &50000); // supply and borrow STABLE for 80% utilization (close to target) - let requests: Vec = vec![ + let requests: SVec = svec![ &fixture.env, Request { request_type: 2, @@ -121,7 +135,7 @@ pub fn create_fixture_with_data<'a>(wasm: bool) -> TestFixture<'a> { pool_fixture.pool.submit(&frodo, &frodo, &frodo, &requests); // supply and borrow WETH for 50% utilization (below target) - let requests: Vec = vec![ + let requests: SVec = svec![ &fixture.env, Request { request_type: 2, @@ -137,7 +151,7 @@ pub fn create_fixture_with_data<'a>(wasm: bool) -> TestFixture<'a> { pool_fixture.pool.submit(&frodo, &frodo, &frodo, &requests); // supply and borrow XLM for 65% utilization (above target) - let requests: Vec = vec![ + let requests: SVec = svec![ &fixture.env, Request { request_type: 2, @@ -167,7 +181,7 @@ mod tests { #[test] fn test_create_fixture_with_data_wasm() { - let fixture = create_fixture_with_data(true); + let fixture: TestFixture<'_> = create_fixture_with_data(true); let frodo = fixture.users.get(0).unwrap(); let pool_fixture: &PoolFixture = fixture.pools.get(0).unwrap(); diff --git a/test-suites/src/test_fixture.rs b/test-suites/src/test_fixture.rs index df62cc47..ddf11fed 100644 --- a/test-suites/src/test_fixture.rs +++ b/test-suites/src/test_fixture.rs @@ -5,7 +5,7 @@ use crate::backstop::create_backstop; use crate::emitter::create_emitter; use crate::liquidity_pool::{create_lp_pool, LPClient}; use crate::oracle::create_mock_oracle; -use crate::pool::{default_reserve_metadata, POOL_WASM}; +use crate::pool::POOL_WASM; use crate::pool_factory::create_pool_factory; use crate::token::{create_stellar_token, create_token}; use backstop::BackstopClient; @@ -18,7 +18,7 @@ use pool_factory::{PoolFactoryClient, PoolInitMeta}; use sep_40_oracle::testutils::{Asset, MockPriceOracleClient}; use sep_41_token::testutils::MockTokenClient; use soroban_sdk::testutils::{Address as _, BytesN as _, Ledger, LedgerInfo}; -use soroban_sdk::{vec as svec, Address, BytesN, Env, Map, Symbol}; +use soroban_sdk::{vec as svec, Address, BytesN, Env, Map, Symbol, Vec as SVec}; pub const SCALAR_7: i128 = 1_000_0000; pub const SCALAR_9: i128 = 1_000_000_000; @@ -167,18 +167,29 @@ impl TestFixture<'_> { fixture } - pub fn create_pool(&mut self, name: Symbol, backstop_take_rate: u64) { + pub fn create_pool( + &mut self, + name: Symbol, + backstop_take_rate: u64, + reserves: SVec<(Address, ReserveConfig)>, + asset_indexes: Vec, + ) { + let mut fixture_reserves = HashMap::new(); + for i in 0..reserves.len() { + fixture_reserves.insert(*asset_indexes.get(i as usize).unwrap(), i as u32); + } + let pool_id = self.pool_factory.deploy( &self.bombadil, &name, &BytesN::<32>::random(&self.env), &self.oracle.address, &backstop_take_rate, - &svec![&self.env,], ); + PoolClient::new(&self.env, &pool_id).init_initial_reserves(&reserves); self.pools.push(PoolFixture { pool: PoolClient::new(&self.env, &pool_id), - reserves: HashMap::new(), + reserves: fixture_reserves, }); } @@ -186,10 +197,14 @@ impl TestFixture<'_> { &mut self, pool_index: usize, asset_index: TokenIndex, - reserve_config: ReserveConfig, + reserve_config: &ReserveConfig, ) { let mut pool_fixture = self.pools.remove(pool_index); let token = &self.tokens[asset_index]; + pool_fixture + .pool + .queue_init_reserve(&token.address, reserve_config); + self.jump(604800); let index = pool_fixture.pool.init_reserve(&token.address); pool_fixture.reserves.insert(asset_index, index); self.pools.insert(pool_index, pool_fixture); diff --git a/test-suites/tests/test_pool.rs b/test-suites/tests/test_pool.rs index 195bc0b0..65076f15 100644 --- a/test-suites/tests/test_pool.rs +++ b/test-suites/tests/test_pool.rs @@ -548,7 +548,6 @@ fn test_pool_config() { &Address::generate(&fixture.env), &Address::generate(&fixture.env), &Address::generate(&fixture.env), - &vec![&fixture.env], ); assert!(result.is_err()); @@ -592,7 +591,9 @@ fn test_pool_config() { let mut reserve_config = default_reserve_metadata(); reserve_config.l_factor = 0_500_0000; reserve_config.c_factor = 0_200_0000; - pool_fixture.pool.init_reserve(&blnd.address); + pool_fixture + .pool + .queue_init_reserve(&blnd.address, &reserve_config); assert_eq!( fixture.env.auths()[0], ( @@ -600,7 +601,7 @@ fn test_pool_config() { AuthorizedInvocation { function: AuthorizedFunction::Contract(( pool_fixture.pool.address.clone(), - Symbol::new(&fixture.env, "init_reserve"), + Symbol::new(&fixture.env, "queue_init_reserve"), vec![ &fixture.env, blnd.address.to_val(), @@ -611,6 +612,9 @@ fn test_pool_config() { } ) ); + fixture.jump(604800); // 1 week + pool_fixture.pool.init_reserve(&blnd.address); + let new_reserve_config = fixture.read_reserve_config(0, TokenIndex::BLND); assert_eq!(new_reserve_config.l_factor, 0_500_0000); assert_eq!(new_reserve_config.c_factor, 0_200_0000); @@ -627,11 +631,7 @@ fn test_pool_config() { &fixture.env, ( pool_fixture.pool.address.clone(), - ( - Symbol::new(&fixture.env, "init_reserve"), - fixture.bombadil.clone() - ) - .into_val(&fixture.env), + (Symbol::new(&fixture.env, "init_reserve"),).into_val(&fixture.env), event_data.into_val(&fixture.env) ) ] From a3597ff6f201d311a97f65f4f6031a1592bdc1b8 Mon Sep 17 00:00:00 2001 From: markuspluna <59978114+markuspluna@users.noreply.github.com> Date: Tue, 19 Dec 2023 17:42:42 -0600 Subject: [PATCH 4/8] Pool: Simplified status flow --- pool-factory/src/test.rs | 2 +- pool/src/contract.rs | 69 ++----- pool/src/pool/config.rs | 268 ++++++++++---------------- pool/src/pool/mod.rs | 5 +- pool/src/pool/status.rs | 66 ++++++- pool/src/storage.rs | 16 +- test-suites/src/pool.rs | 2 +- test-suites/src/setup.rs | 23 +-- test-suites/src/test_fixture.rs | 23 +-- test-suites/tests/test_liquidation.rs | 41 ++-- test-suites/tests/test_pool.rs | 23 ++- 11 files changed, 239 insertions(+), 299 deletions(-) diff --git a/pool-factory/src/test.rs b/pool-factory/src/test.rs index 50bed872..ced86f29 100644 --- a/pool-factory/src/test.rs +++ b/pool-factory/src/test.rs @@ -92,7 +92,7 @@ fn test_pool_factory() { pool::PoolConfig { oracle: oracle, bstop_rate: backstop_rate, - status: 3 + status: 6 } ); assert_eq!( diff --git a/pool/src/contract.rs b/pool/src/contract.rs index cb301df8..ba30d477 100644 --- a/pool/src/contract.rs +++ b/pool/src/contract.rs @@ -57,37 +57,26 @@ pub trait Pool { /// If the caller is not the admin fn update_pool(e: Env, backstop_take_rate: u64); - /// (Admin only) Initializes the first pool reserves + /// (Admin only) Queues setting data for a reserve in the pool /// /// ### Arguments - /// * `reserves` - A vector of tuples of the form `(asset, config) /// * `asset` - The underlying asset to add as a reserve /// * `config` - The ReserveConfig for the reserve /// /// ### Panics /// If the caller is not the admin - fn init_initial_reserves(e: Env, reserves: Vec<(Address, ReserveConfig)>); + fn queue_set_reserve(e: Env, asset: Address, metadata: ReserveConfig); - /// (Admin only) Queues the initialization of a reserve in the pool - /// - /// ### Arguments - /// * `asset` - The underlying asset to add as a reserve - /// * `config` - The ReserveConfig for the reserve - /// - /// ### Panics - /// If the caller is not the admin - fn queue_init_reserve(e: Env, asset: Address, metadata: ReserveConfig); - - /// (Admin only) Cancels the queued initialization of a reserve in the pool + /// (Admin only) Cancels the queued set of a reserve in the pool /// /// ### Arguments /// * `asset` - The underlying asset to add as a reserve /// /// ### Panics /// If the caller is not the admin or the reserve is not queued for initialization - fn cancel_init_reserve(e: Env, asset: Address); + fn cancel_set_reserve(e: Env, asset: Address); - /// (Admin only) Executes the queued initialization of a reserve in the pool + /// (Admin only) Executes the queued set of a reserve in the pool /// /// ### Arguments /// * `asset` - The underlying asset to add as a reserve @@ -96,17 +85,7 @@ pub trait Pool { /// If the reserve is not queued for initialization /// or is already setup /// or has invalid metadata - fn init_reserve(e: Env, asset: Address) -> u32; - - /// (Admin only) Update a reserve in the pool - /// - /// ### Arguments - /// * `asset` - The underlying asset to add as a reserve - /// * `config` - The ReserveConfig for the reserve - /// - /// ### Panics - /// If the caller is not the admin or the reserve does not exist - fn update_reserve(e: Env, asset: Address, config: ReserveConfig); + fn set_reserve(e: Env, asset: Address) -> u32; /// Fetch the positions for an address /// @@ -292,52 +271,36 @@ impl Pool for PoolContract { .publish((Symbol::new(&e, "update_pool"), admin), backstop_take_rate); } - fn init_initial_reserves(e: Env, reserves: Vec<(Address, ReserveConfig)>) { + fn queue_set_reserve(e: Env, asset: Address, metadata: ReserveConfig) { storage::extend_instance(&e); let admin = storage::get_admin(&e); admin.require_auth(); - pool::execute_initialize_initial_reserves(&e, &reserves); - } - - fn queue_init_reserve(e: Env, asset: Address, metadata: ReserveConfig) { - storage::extend_instance(&e); - let admin = storage::get_admin(&e); - admin.require_auth(); - - pool::execute_queue_reserve_initialization(&e, &asset, &metadata); + pool::execute_queue_set_reserve(&e, &asset, &metadata); e.events().publish( - (Symbol::new(&e, "queue_init_reserve"), admin), + (Symbol::new(&e, "queue_set_reserve"), admin), (asset, metadata), ); } - fn cancel_init_reserve(e: Env, asset: Address) { + fn cancel_set_reserve(e: Env, asset: Address) { storage::extend_instance(&e); let admin = storage::get_admin(&e); admin.require_auth(); - pool::execute_cancel_queued_reserve_initialization(&e, &asset); + pool::execute_cancel_queued_set_reserve(&e, &asset); e.events() - .publish((Symbol::new(&e, "cancel_init_reserve"), admin), asset); - } - - fn init_reserve(e: Env, asset: Address) -> u32 { - let index = pool::execute_initialize_queued_reserve(&e, &asset); - index + .publish((Symbol::new(&e, "cancel_set_reserve"), admin), asset); } - fn update_reserve(e: Env, asset: Address, config: ReserveConfig) { - storage::extend_instance(&e); - let admin = storage::get_admin(&e); - admin.require_auth(); - - pool::execute_update_reserve(&e, &asset, &config); + fn set_reserve(e: Env, asset: Address) -> u32 { + let index = pool::execute_set_queued_reserve(&e, &asset); e.events() - .publish((Symbol::new(&e, "update_reserve"), admin), asset); + .publish((Symbol::new(&e, "set_reserve"),), (asset, index)); + index } fn get_positions(e: Env, address: Address) -> Positions { diff --git a/pool/src/pool/config.rs b/pool/src/pool/config.rs index c9a0e477..9eefd53e 100644 --- a/pool/src/pool/config.rs +++ b/pool/src/pool/config.rs @@ -3,7 +3,7 @@ use crate::{ errors::PoolError, storage::{self, PoolConfig, QueuedReserveInit, ReserveConfig, ReserveData}, }; -use soroban_sdk::{panic_with_error, Address, Env, Symbol, Vec}; +use soroban_sdk::{panic_with_error, Address, Env, Symbol}; use super::pool::Pool; @@ -38,7 +38,7 @@ pub fn execute_initialize( &PoolConfig { oracle: oracle.clone(), bstop_rate: *bstop_rate, - status: 3, + status: 6, }, ); storage::set_blnd_token(e, blnd_id); @@ -56,61 +56,71 @@ pub fn execute_update_pool(e: &Env, backstop_take_rate: u64) { storage::set_pool_config(e, &pool_config); } -/// Execute initial reserve initialization -pub fn execute_initialize_initial_reserves(e: &Env, reserves: &Vec<(Address, ReserveConfig)>) { - // confirm the pool has no reserves - if storage::get_res_list(e).len() > 0 { - panic_with_error!(e, PoolError::AlreadyInitialized); - } - // initialize reserves - for (asset, config) in reserves.iter() { - initialize_reserve(e, &asset, &config); - } -} - /// Execute a queueing a reserve initialization for the pool -pub fn execute_queue_reserve_initialization(e: &Env, asset: &Address, metadata: &ReserveConfig) { - storage::set_queued_reserve_init( +pub fn execute_queue_set_reserve(e: &Env, asset: &Address, metadata: &ReserveConfig) { + require_valid_reserve_metadata(e, metadata); + let mut unlock_time = e.ledger().timestamp(); + // require a timelock if pool status is not setup + if storage::get_pool_config(e).status != 6 { + unlock_time += SECONDS_PER_WEEK; + } + storage::set_queued_reserve_set( &e, &QueuedReserveInit { new_config: metadata.clone(), - unlock_time: e.ledger().timestamp() + SECONDS_PER_WEEK, + unlock_time, }, &asset, ); } /// Execute cancelling a queueing a reserve initialization for the pool -pub fn execute_cancel_queued_reserve_initialization(e: &Env, asset: &Address) { - storage::del_queued_reserve_init(&e, &asset); +pub fn execute_cancel_queued_set_reserve(e: &Env, asset: &Address) { + storage::del_queued_reserve_set(&e, &asset); } /// Execute a queued reserve initialization for the pool -pub fn execute_initialize_queued_reserve(e: &Env, asset: &Address) -> u32 { - let queued_init = storage::get_queued_reserve_init(e, asset); +pub fn execute_set_queued_reserve(e: &Env, asset: &Address) -> u32 { + let queued_init = storage::get_queued_reserve_set(e, asset); if queued_init.unlock_time > e.ledger().timestamp() { panic_with_error!(e, PoolError::InitNotUnlocked); } - // remove queued init - storage::del_queued_reserve_init(e, asset); + // remove queued reserve + storage::del_queued_reserve_set(e, asset); // initialize reserve - let index = initialize_reserve(e, asset, &queued_init.new_config); - - index + initialize_reserve(e, asset, &queued_init.new_config) } -/// Initialize a reserve for the pool +/// sets reserve data for the pool fn initialize_reserve(e: &Env, asset: &Address, config: &ReserveConfig) -> u32 { + let index: u32; + // if reserve already exists, ensure index and scalar do not change if storage::has_res(e, asset) { - panic_with_error!(e, PoolError::AlreadyInitialized); + // accrue and store reserve data to the ledger + let pool = Pool::load(e); + let reserve = pool.load_reserve(e, asset); + reserve.store(e); + index = reserve.index; + if reserve.scalar != 10i128.pow(config.decimals) { + panic_with_error!(e, PoolError::InvalidReserveMetadata); + } + } else { + index = storage::push_res_list(e, asset); + let init_data = ReserveData { + b_rate: 1_000_000_000, + d_rate: 1_000_000_000, + ir_mod: 1_000_000_000, + d_supply: 0, + b_supply: 0, + last_time: e.ledger().timestamp(), + backstop_credit: 0, + }; + storage::set_res_data(e, asset, &init_data); } - require_valid_reserve_metadata(e, config); - let index = storage::push_res_list(e, asset); - let reserve_config = ReserveConfig { index, decimals: config.decimals, @@ -124,39 +134,8 @@ fn initialize_reserve(e: &Env, asset: &Address, config: &ReserveConfig) -> u32 { reactivity: config.reactivity, }; storage::set_res_config(e, asset, &reserve_config); - let init_data = ReserveData { - b_rate: 1_000_000_000, - d_rate: 1_000_000_000, - ir_mod: 1_000_000_000, - d_supply: 0, - b_supply: 0, - last_time: e.ledger().timestamp(), - backstop_credit: 0, - }; - storage::set_res_data(e, asset, &init_data); - e.events() - .publish((Symbol::new(&e, "init_reserve"),), (asset, index)); - index -} - -/// Update a reserve in the pool -pub fn execute_update_reserve(e: &Env, asset: &Address, config: &ReserveConfig) { - require_valid_reserve_metadata(e, config); - let pool = Pool::load(e); - if pool.config.status == 2 { - panic_with_error!(e, PoolError::InvalidPoolStatus); - } - - // accrue and store reserve data to the ledger - let reserve = pool.load_reserve(e, asset); - reserve.store(e); - - // force index to remain constant and only allow metadata based changes - let mut new_config = config.clone(); - new_config.index = reserve.index; - - storage::set_res_config(e, asset, &new_config); + index } #[allow(clippy::zero_prefixed_literal)] @@ -180,7 +159,6 @@ mod tests { use super::*; use soroban_sdk::testutils::{Address as _, Ledger, LedgerInfo}; - use soroban_sdk::vec; #[test] fn test_execute_initialize() { @@ -194,21 +172,6 @@ mod tests { let backstop_address = Address::generate(&e); let blnd_id = Address::generate(&e); let usdc_id = Address::generate(&e); - let (asset_id_0, _) = testutils::create_token_contract(&e, &admin); - let (asset_id_1, _) = testutils::create_token_contract(&e, &admin); - - let metadata = ReserveConfig { - index: 0, - decimals: 7, - c_factor: 0_7500000, - l_factor: 0_7500000, - util: 0_5000000, - max_util: 0_9500000, - r_one: 0_0500000, - r_two: 0_5000000, - r_three: 1_5000000, - reactivity: 100, - }; e.as_contract(&pool, || { execute_initialize( @@ -226,7 +189,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, 3); + assert_eq!(pool_config.status, 6); 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); @@ -273,13 +236,12 @@ mod tests { }); } #[test] - fn test_initialize_initial_reserves() { + fn test_queue_initial_reserve() { let e = Env::default(); let pool = testutils::create_pool(&e); let bombadil = Address::generate(&e); let (asset_id_0, _) = testutils::create_token_contract(&e, &bombadil); - let (asset_id_1, _) = testutils::create_token_contract(&e, &bombadil); let metadata = ReserveConfig { index: 0, @@ -293,17 +255,17 @@ mod tests { r_three: 1_5000000, reactivity: 100, }; + let pool_config = PoolConfig { + oracle: Address::generate(&e), + bstop_rate: 0_100_000_000, + status: 6, + }; e.as_contract(&pool, || { - execute_initialize_initial_reserves( - &e, - &vec![ - &e, - (asset_id_0.clone(), metadata.clone()), - (asset_id_1.clone(), metadata.clone()), - ], - ); - let res_config_0 = storage::get_res_config(&e, &asset_id_0); - let res_config_1 = storage::get_res_config(&e, &asset_id_1); + storage::set_pool_config(&e, &pool_config); + execute_queue_set_reserve(&e, &asset_id_0, &metadata); + let queued_res = storage::get_queued_reserve_set(&e, &asset_id_0); + let res_config_0 = queued_res.new_config; + assert_eq!(queued_res.unlock_time, e.ledger().timestamp()); assert_eq!(res_config_0.decimals, metadata.decimals); assert_eq!(res_config_0.c_factor, metadata.c_factor); assert_eq!(res_config_0.l_factor, metadata.l_factor); @@ -314,39 +276,9 @@ mod tests { assert_eq!(res_config_0.r_three, metadata.r_three); assert_eq!(res_config_0.reactivity, metadata.reactivity); assert_eq!(res_config_0.index, 0); - assert_eq!(res_config_1.index, 1); }); } - #[test] - #[should_panic(expected = "Error(Contract, #3)")] - fn test_initialize_initial_reserves_panics_if_reserves_exist() { - let e = Env::default(); - let pool = testutils::create_pool(&e); - let bombadil = Address::generate(&e); - - let (asset_id_0, _) = testutils::create_token_contract(&e, &bombadil); - let (asset_id_1, _) = testutils::create_token_contract(&e, &bombadil); - let metadata = ReserveConfig { - index: 0, - decimals: 7, - c_factor: 0_7500000, - l_factor: 0_7500000, - util: 0_5000000, - max_util: 0_9500000, - r_one: 0_0500000, - r_two: 0_5000000, - r_three: 1_5000000, - reactivity: 100, - }; - e.as_contract(&pool, || { - initialize_reserve(&e, &asset_id_0, &metadata); - execute_initialize_initial_reserves( - &e, - &vec![&e, (asset_id_1.clone(), metadata.clone())], - ); - }); - } #[test] fn test_execute_queue_reserve_initialization() { let e = Env::default(); @@ -367,9 +299,15 @@ mod tests { r_three: 1_5000000, reactivity: 100, }; + let pool_config = PoolConfig { + oracle: Address::generate(&e), + bstop_rate: 0_100_000_000, + status: 0, + }; e.as_contract(&pool, || { - execute_queue_reserve_initialization(&e, &asset_id_0, &metadata); - let queued_init = storage::get_queued_reserve_init(&e, &asset_id_0); + storage::set_pool_config(&e, &pool_config); + execute_queue_set_reserve(&e, &asset_id_0, &metadata); + let queued_init = storage::get_queued_reserve_set(&e, &asset_id_0); assert_eq!(queued_init.new_config.decimals, metadata.decimals); assert_eq!(queued_init.new_config.c_factor, metadata.c_factor); assert_eq!(queued_init.new_config.l_factor, metadata.l_factor); @@ -408,7 +346,7 @@ mod tests { reactivity: 100, }; e.as_contract(&pool, || { - storage::set_queued_reserve_init( + storage::set_queued_reserve_set( &e, &QueuedReserveInit { new_config: metadata.clone(), @@ -416,8 +354,8 @@ mod tests { }, &asset_id_0, ); - execute_cancel_queued_reserve_initialization(&e, &asset_id_0); - storage::get_queued_reserve_init(&e, &asset_id_0); + execute_cancel_queued_set_reserve(&e, &asset_id_0); + storage::get_queued_reserve_set(&e, &asset_id_0); }); } #[test] @@ -441,7 +379,7 @@ mod tests { reactivity: 100, }; e.as_contract(&pool, || { - storage::set_queued_reserve_init( + storage::set_queued_reserve_set( &e, &QueuedReserveInit { new_config: metadata.clone(), @@ -449,7 +387,7 @@ mod tests { }, &asset_id_0, ); - execute_initialize_queued_reserve(&e, &asset_id_0); + execute_set_queued_reserve(&e, &asset_id_0); let res_config_0: ReserveConfig = storage::get_res_config(&e, &asset_id_0); assert_eq!(res_config_0.decimals, metadata.decimals); assert_eq!(res_config_0.c_factor, metadata.c_factor); @@ -485,7 +423,7 @@ mod tests { reactivity: 100, }; e.as_contract(&pool, || { - storage::set_queued_reserve_init( + storage::set_queued_reserve_set( &e, &QueuedReserveInit { new_config: metadata.clone(), @@ -493,7 +431,7 @@ mod tests { }, &asset_id_0, ); - execute_initialize_queued_reserve(&e, &asset_id_0); + execute_set_queued_reserve(&e, &asset_id_0); }); } #[test] @@ -537,37 +475,9 @@ mod tests { }); } - #[test] - #[should_panic(expected = "Error(Contract, #3)")] - fn test_initialize_reserve_blocks_duplicates() { - let e = Env::default(); - let pool = testutils::create_pool(&e); - let bombadil = Address::generate(&e); - let (asset_id, _) = testutils::create_token_contract(&e, &bombadil); - - let metadata = ReserveConfig { - index: 0, - decimals: 7, - c_factor: 0_7500000, - l_factor: 0_7500000, - util: 0_5000000, - max_util: 0_9500000, - r_one: 0_0500000, - r_two: 0_5000000, - r_three: 1_5000000, - reactivity: 100, - }; - e.as_contract(&pool, || { - initialize_reserve(&e, &asset_id, &metadata); - let res_config = storage::get_res_config(&e, &asset_id); - assert_eq!(res_config.index, 0); - initialize_reserve(&e, &asset_id, &metadata); - }); - } - #[test] #[should_panic(expected = "Error(Contract, #6)")] - fn test_initialize_reserve_validates_metadata() { + fn test_queue_set_reserve_validates_metadata() { let e = Env::default(); let pool = testutils::create_pool(&e); let bombadil = Address::generate(&e); @@ -577,7 +487,7 @@ mod tests { index: 0, decimals: 7, c_factor: 0_7500000, - l_factor: 0_7500000, + l_factor: 1_7500000, util: 1_0000000, max_util: 0_9500000, r_one: 0_0500000, @@ -585,11 +495,14 @@ mod tests { r_three: 1_5000000, reactivity: 100, }; + let pool_config = PoolConfig { + oracle: Address::generate(&e), + bstop_rate: 0_100_000_000, + status: 0, + }; e.as_contract(&pool, || { - initialize_reserve(&e, &asset_id, &metadata); - let res_config = storage::get_res_config(&e, &asset_id); - assert_eq!(res_config.index, 0); - initialize_reserve(&e, &asset_id, &metadata); + storage::set_pool_config(&e, &pool_config); + execute_queue_set_reserve(&e, &asset_id, &metadata); }); } @@ -648,8 +561,15 @@ mod tests { storage::set_pool_config(&e, &pool_config); let res_config_old = storage::get_res_config(&e, &underlying); - - execute_update_reserve(&e, &underlying, &new_metadata); + storage::set_queued_reserve_set( + &e, + &QueuedReserveInit { + new_config: new_metadata.clone(), + unlock_time: e.ledger().timestamp(), + }, + &underlying, + ); + execute_set_queued_reserve(&e, &underlying); let res_config_updated = storage::get_res_config(&e, &underlying); assert_eq!(res_config_updated.decimals, new_metadata.decimals); assert_eq!(res_config_updated.c_factor, new_metadata.c_factor); @@ -672,7 +592,7 @@ mod tests { #[test] #[should_panic(expected = "Error(Contract, #6)")] - fn test_execute_update_reserve_validates_metadata() { + fn test_execute_update_reserve_validates_decimals() { let e = Env::default(); e.mock_all_auths(); e.ledger().set(LedgerInfo { @@ -695,7 +615,7 @@ mod tests { let new_metadata = ReserveConfig { index: 99, - decimals: 7, + decimals: 8, c_factor: 0_7500000, l_factor: 0_7500000, util: 1_0777777, @@ -714,7 +634,15 @@ mod tests { e.as_contract(&pool, || { storage::set_pool_config(&e, &pool_config); - execute_update_reserve(&e, &underlying, &new_metadata); + storage::set_queued_reserve_set( + &e, + &QueuedReserveInit { + new_config: new_metadata.clone(), + unlock_time: e.ledger().timestamp(), + }, + &underlying, + ); + execute_set_queued_reserve(&e, &underlying); }); } diff --git a/pool/src/pool/mod.rs b/pool/src/pool/mod.rs index 8baf272a..9097e5e1 100644 --- a/pool/src/pool/mod.rs +++ b/pool/src/pool/mod.rs @@ -6,9 +6,8 @@ pub use bad_debt::{burn_backstop_bad_debt, transfer_bad_debt_to_backstop}; mod config; pub use config::{ - execute_cancel_queued_reserve_initialization, execute_initialize, - execute_initialize_initial_reserves, execute_initialize_queued_reserve, - execute_queue_reserve_initialization, execute_update_pool, execute_update_reserve, + execute_cancel_queued_set_reserve, execute_initialize, execute_queue_set_reserve, + execute_set_queued_reserve, execute_update_pool, }; mod health_factor; diff --git a/pool/src/pool/status.rs b/pool/src/pool/status.rs index 283d8da0..c50e4092 100644 --- a/pool/src/pool/status.rs +++ b/pool/src/pool/status.rs @@ -23,6 +23,11 @@ pub fn execute_update_pool_status(e: &Env) -> u32 { } match pool_config.status { + // Setup + 6 => { + // Setup supersedes all other statuses + panic_with_error!(e, PoolError::StatusNotAllowed); + } // Admin frozen 4 => { // Admin frozen supersedes all other statuses @@ -71,10 +76,7 @@ pub fn execute_set_pool_status(e: &Env, pool_status: u32) { 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 @@ -94,6 +96,14 @@ pub fn execute_set_pool_status(e: &Env, pool_status: u32) { // Admin On-Ice pool_config.status = 2; } + 3 => { + // Q4w must be under 75% for admin to set permissionless On-Ice + if pool_backstop_data.q4w_pct >= 0_7500000 { + panic_with_error!(e, PoolError::StatusNotAllowed); + } + // Admin On-Ice + pool_config.status = 3; + } 4 => { // Admin can always freeze the pool // Admin Frozen @@ -891,11 +901,11 @@ mod tests { } #[test] - #[should_panic(expected = "Error(Auth, InvalidAction)")] + #[should_panic(expected = "Error(Contract, #8)")] 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::generate(&e); @@ -934,6 +944,50 @@ mod tests { }); } + #[test] + #[should_panic(expected = "Error(Contract, #8)")] + fn test_update_pool_status_setup() { + 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: 6, + }; + e.as_contract(&pool_id, || { + storage::set_admin(&e, &bombadil); + storage::set_pool_config(&e, &pool_config); + + execute_update_pool_status(&e); + }); + } + #[test] fn test_admin_update_pool_status_unfreeze() { let e = Env::default(); diff --git a/pool/src/storage.rs b/pool/src/storage.rs index c4225163..5635b51c 100644 --- a/pool/src/storage.rs +++ b/pool/src/storage.rs @@ -367,14 +367,14 @@ pub fn has_res(e: &Env, asset: &Address) -> bool { e.storage().persistent().has(&key) } -/// Fetch a queued reserve initialization +/// Fetch a queued reserve set /// /// ### Arguments /// * `asset` - The contract address of the asset /// /// ### Panics -/// If the reserve initialization has not been queued -pub fn get_queued_reserve_init(e: &Env, asset: &Address) -> QueuedReserveInit { +/// If the reserve set has not been queued +pub fn get_queued_reserve_set(e: &Env, asset: &Address) -> QueuedReserveInit { let key = PoolDataKey::ResInit(asset.clone()); e.storage() .persistent() @@ -385,12 +385,12 @@ pub fn get_queued_reserve_init(e: &Env, asset: &Address) -> QueuedReserveInit { .unwrap_optimized() } -/// Set a new queued reserve initialization +/// Set a new queued reserve set /// /// ### Arguments /// * `asset` - The contract address of the asset /// * `config` - The reserve configuration for the asset -pub fn set_queued_reserve_init(e: &Env, res_init: &QueuedReserveInit, asset: &Address) { +pub fn set_queued_reserve_set(e: &Env, res_init: &QueuedReserveInit, asset: &Address) { let key = PoolDataKey::ResInit(asset.clone()); e.storage() .persistent() @@ -400,14 +400,14 @@ pub fn set_queued_reserve_init(e: &Env, res_init: &QueuedReserveInit, asset: &Ad .extend_ttl(&key, LEDGER_THRESHOLD_SHARED, LEDGER_BUMP_SHARED); } -/// Delete a queued reserve initialization +/// Delete a queued reserve set /// /// ### Arguments /// * `asset` - The contract address of the asset /// /// ### Panics -/// If the reserve initialization has not been queued -pub fn del_queued_reserve_init(e: &Env, asset: &Address) { +/// If the reserve set has not been queued +pub fn del_queued_reserve_set(e: &Env, asset: &Address) { let key = PoolDataKey::ResInit(asset.clone()); e.storage().persistent().remove(&key); } diff --git a/test-suites/src/pool.rs b/test-suites/src/pool.rs index ed029dde..f290593c 100644 --- a/test-suites/src/pool.rs +++ b/test-suites/src/pool.rs @@ -15,7 +15,7 @@ pub fn default_reserve_metadata() -> ReserveConfig { r_one: 0_0500000, r_two: 0_5000000, r_three: 1_5000000, - reactivity: 0_000_002_000, // 10e-5 + reactivity: 0, // 10e-5 0_000_002_000 index: 0, } } diff --git a/test-suites/src/setup.rs b/test-suites/src/setup.rs index d213d654..1e98c32f 100644 --- a/test-suites/src/setup.rs +++ b/test-suites/src/setup.rs @@ -33,41 +33,27 @@ pub fn create_fixture_with_data<'a>(wasm: bool) -> TestFixture<'a> { ); // create pool + fixture.create_pool(Symbol::new(&fixture.env, "Teapot"), 0_100_000_000); let mut stable_config = default_reserve_metadata(); stable_config.decimals = 6; stable_config.c_factor = 0_900_0000; stable_config.l_factor = 0_950_0000; stable_config.util = 0_850_0000; + fixture.create_pool_reserve(0, TokenIndex::STABLE, &stable_config); let mut xlm_config = default_reserve_metadata(); xlm_config.c_factor = 0_750_0000; xlm_config.l_factor = 0_750_0000; xlm_config.util = 0_500_0000; + fixture.create_pool_reserve(0, TokenIndex::XLM, &xlm_config); let mut weth_config = default_reserve_metadata(); weth_config.decimals = 9; weth_config.c_factor = 0_800_0000; weth_config.l_factor = 0_800_0000; weth_config.util = 0_700_0000; - - fixture.create_pool( - Symbol::new(&fixture.env, "Teapot"), - 0_100_000_000, - svec![ - &fixture.env, - ( - fixture.tokens[TokenIndex::STABLE].address.clone(), - stable_config - ), - (fixture.tokens[TokenIndex::XLM].address.clone(), xlm_config), - ( - fixture.tokens[TokenIndex::WETH].address.clone(), - weth_config - ), - ], - vec![TokenIndex::STABLE, TokenIndex::XLM, TokenIndex::WETH], - ); + fixture.create_pool_reserve(0, TokenIndex::WETH, &weth_config); // enable emissions for pool let pool_fixture = &fixture.pools[0]; @@ -95,6 +81,7 @@ pub fn create_fixture_with_data<'a>(wasm: bool) -> TestFixture<'a> { fixture .backstop .add_reward(&pool_fixture.pool.address, &Address::generate(&fixture.env)); + pool_fixture.pool.set_status(&3); pool_fixture.pool.update_status(); // enable emissions diff --git a/test-suites/src/test_fixture.rs b/test-suites/src/test_fixture.rs index ddf11fed..8cc4a87b 100644 --- a/test-suites/src/test_fixture.rs +++ b/test-suites/src/test_fixture.rs @@ -18,7 +18,7 @@ use pool_factory::{PoolFactoryClient, PoolInitMeta}; use sep_40_oracle::testutils::{Asset, MockPriceOracleClient}; use sep_41_token::testutils::MockTokenClient; use soroban_sdk::testutils::{Address as _, BytesN as _, Ledger, LedgerInfo}; -use soroban_sdk::{vec as svec, Address, BytesN, Env, Map, Symbol, Vec as SVec}; +use soroban_sdk::{vec as svec, Address, BytesN, Env, Map, Symbol}; pub const SCALAR_7: i128 = 1_000_0000; pub const SCALAR_9: i128 = 1_000_000_000; @@ -167,18 +167,7 @@ impl TestFixture<'_> { fixture } - pub fn create_pool( - &mut self, - name: Symbol, - backstop_take_rate: u64, - reserves: SVec<(Address, ReserveConfig)>, - asset_indexes: Vec, - ) { - let mut fixture_reserves = HashMap::new(); - for i in 0..reserves.len() { - fixture_reserves.insert(*asset_indexes.get(i as usize).unwrap(), i as u32); - } - + pub fn create_pool(&mut self, name: Symbol, backstop_take_rate: u64) { let pool_id = self.pool_factory.deploy( &self.bombadil, &name, @@ -186,10 +175,9 @@ impl TestFixture<'_> { &self.oracle.address, &backstop_take_rate, ); - PoolClient::new(&self.env, &pool_id).init_initial_reserves(&reserves); self.pools.push(PoolFixture { pool: PoolClient::new(&self.env, &pool_id), - reserves: fixture_reserves, + reserves: HashMap::new(), }); } @@ -203,9 +191,8 @@ impl TestFixture<'_> { let token = &self.tokens[asset_index]; pool_fixture .pool - .queue_init_reserve(&token.address, reserve_config); - self.jump(604800); - let index = pool_fixture.pool.init_reserve(&token.address); + .queue_set_reserve(&token.address, reserve_config); + let index = pool_fixture.pool.set_reserve(&token.address); pool_fixture.reserves.insert(asset_index, index); self.pools.insert(pool_index, pool_fixture); } diff --git a/test-suites/tests/test_liquidation.rs b/test-suites/tests/test_liquidation.rs index 705f2720..a4cb1fd0 100644 --- a/test-suites/tests/test_liquidation.rs +++ b/test-suites/tests/test_liquidation.rs @@ -19,22 +19,31 @@ fn test_liquidations() { let pool_fixture = &fixture.pools[0]; // Disable rate modifiers - let mut usdc_config: ReserveConfig = fixture.read_reserve_config(0, TokenIndex::STABLE); - usdc_config.reactivity = 0; - pool_fixture - .pool - .update_reserve(&fixture.tokens[TokenIndex::STABLE].address, &usdc_config); - let mut xlm_config: ReserveConfig = fixture.read_reserve_config(0, TokenIndex::XLM); - xlm_config.reactivity = 0; - pool_fixture - .pool - .update_reserve(&fixture.tokens[TokenIndex::XLM].address, &xlm_config); - let mut weth_config: ReserveConfig = fixture.read_reserve_config(0, TokenIndex::WETH); - weth_config.reactivity = 0; - pool_fixture - .pool - .update_reserve(&fixture.tokens[TokenIndex::WETH].address, &weth_config); - + // let mut usdc_config: ReserveConfig = fixture.read_reserve_config(0, TokenIndex::STABLE); + // usdc_config.reactivity = 0; + // pool_fixture + // .pool + // .queue_set_reserve(&fixture.tokens[TokenIndex::STABLE].address, &usdc_config); + // let mut xlm_config: ReserveConfig = fixture.read_reserve_config(0, TokenIndex::XLM); + // xlm_config.reactivity = 0; + // pool_fixture + // .pool + // .queue_set_reserve(&fixture.tokens[TokenIndex::XLM].address, &xlm_config); + // let mut weth_config: ReserveConfig = fixture.read_reserve_config(0, TokenIndex::WETH); + // weth_config.reactivity = 0; + // pool_fixture + // .pool + // .queue_set_reserve(&fixture.tokens[TokenIndex::WETH].address, &weth_config); + // fixture.jump(60 * 60 * 24 * 7); + // pool_fixture + // .pool + // .set_reserve(&fixture.tokens[TokenIndex::STABLE].address); + // pool_fixture + // .pool + // .set_reserve(&fixture.tokens[TokenIndex::XLM].address); + // pool_fixture + // .pool + // .set_reserve(&fixture.tokens[TokenIndex::WETH].address); // Create a user let samwise = Address::generate(&fixture.env); //sam will be supplying XLM and borrowing STABLE diff --git a/test-suites/tests/test_pool.rs b/test-suites/tests/test_pool.rs index 65076f15..2426c96a 100644 --- a/test-suites/tests/test_pool.rs +++ b/test-suites/tests/test_pool.rs @@ -593,7 +593,7 @@ fn test_pool_config() { reserve_config.c_factor = 0_200_0000; pool_fixture .pool - .queue_init_reserve(&blnd.address, &reserve_config); + .queue_set_reserve(&blnd.address, &reserve_config); assert_eq!( fixture.env.auths()[0], ( @@ -613,7 +613,7 @@ fn test_pool_config() { ) ); fixture.jump(604800); // 1 week - pool_fixture.pool.init_reserve(&blnd.address); + pool_fixture.pool.set_reserve(&blnd.address); let new_reserve_config = fixture.read_reserve_config(0, TokenIndex::BLND); assert_eq!(new_reserve_config.l_factor, 0_500_0000); @@ -631,7 +631,7 @@ fn test_pool_config() { &fixture.env, ( pool_fixture.pool.address.clone(), - (Symbol::new(&fixture.env, "init_reserve"),).into_val(&fixture.env), + (Symbol::new(&fixture.env, "set_reserve"),).into_val(&fixture.env), event_data.into_val(&fixture.env) ) ] @@ -641,7 +641,7 @@ fn test_pool_config() { reserve_config.c_factor = 0; pool_fixture .pool - .update_reserve(&blnd.address, &reserve_config); + .queue_set_reserve(&blnd.address, &reserve_config); assert_eq!( fixture.env.auths()[0], ( @@ -649,7 +649,7 @@ fn test_pool_config() { AuthorizedInvocation { function: AuthorizedFunction::Contract(( pool_fixture.pool.address.clone(), - Symbol::new(&fixture.env, "update_reserve"), + Symbol::new(&fixture.env, "queue_set_reserve"), vec![ &fixture.env, blnd.address.to_val(), @@ -660,6 +660,19 @@ fn test_pool_config() { } ) ); + fixture.jump(604800); // 1 week + pool_fixture.pool.set_reserve(&blnd.address); + assert_eq!( + event, + vec![ + &fixture.env, + ( + pool_fixture.pool.address.clone(), + (Symbol::new(&fixture.env, "set_reserve"),).into_val(&fixture.env), + event_data.into_val(&fixture.env) + ) + ] + ); let new_reserve_config = fixture.read_reserve_config(0, TokenIndex::BLND); assert_eq!(new_reserve_config.l_factor, 0_500_0000); assert_eq!(new_reserve_config.c_factor, 0); From 692aca874a2398b61318cb65060842fb2058f998 Mon Sep 17 00:00:00 2001 From: markuspluna <59978114+markuspluna@users.noreply.github.com> Date: Tue, 19 Dec 2023 18:24:09 -0600 Subject: [PATCH 5/8] test fixes --- test-suites/src/pool.rs | 2 +- test-suites/tests/test_liquidation.rs | 54 ++++++++++++++------------- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/test-suites/src/pool.rs b/test-suites/src/pool.rs index f290593c..ed029dde 100644 --- a/test-suites/src/pool.rs +++ b/test-suites/src/pool.rs @@ -15,7 +15,7 @@ pub fn default_reserve_metadata() -> ReserveConfig { r_one: 0_0500000, r_two: 0_5000000, r_three: 1_5000000, - reactivity: 0, // 10e-5 0_000_002_000 + reactivity: 0_000_002_000, // 10e-5 index: 0, } } diff --git a/test-suites/tests/test_liquidation.rs b/test-suites/tests/test_liquidation.rs index a4cb1fd0..f1eaf455 100644 --- a/test-suites/tests/test_liquidation.rs +++ b/test-suites/tests/test_liquidation.rs @@ -19,31 +19,35 @@ fn test_liquidations() { let pool_fixture = &fixture.pools[0]; // Disable rate modifiers - // let mut usdc_config: ReserveConfig = fixture.read_reserve_config(0, TokenIndex::STABLE); - // usdc_config.reactivity = 0; - // pool_fixture - // .pool - // .queue_set_reserve(&fixture.tokens[TokenIndex::STABLE].address, &usdc_config); - // let mut xlm_config: ReserveConfig = fixture.read_reserve_config(0, TokenIndex::XLM); - // xlm_config.reactivity = 0; - // pool_fixture - // .pool - // .queue_set_reserve(&fixture.tokens[TokenIndex::XLM].address, &xlm_config); - // let mut weth_config: ReserveConfig = fixture.read_reserve_config(0, TokenIndex::WETH); - // weth_config.reactivity = 0; - // pool_fixture - // .pool - // .queue_set_reserve(&fixture.tokens[TokenIndex::WETH].address, &weth_config); - // fixture.jump(60 * 60 * 24 * 7); - // pool_fixture - // .pool - // .set_reserve(&fixture.tokens[TokenIndex::STABLE].address); - // pool_fixture - // .pool - // .set_reserve(&fixture.tokens[TokenIndex::XLM].address); - // pool_fixture - // .pool - // .set_reserve(&fixture.tokens[TokenIndex::WETH].address); + let mut usdc_config: ReserveConfig = fixture.read_reserve_config(0, TokenIndex::STABLE); + usdc_config.reactivity = 0; + + let mut xlm_config: ReserveConfig = fixture.read_reserve_config(0, TokenIndex::XLM); + xlm_config.reactivity = 0; + let mut weth_config: ReserveConfig = fixture.read_reserve_config(0, TokenIndex::WETH); + weth_config.reactivity = 0; + + fixture.env.as_contract(&fixture.pools[0].pool.address, || { + let key = PoolDataKey::ResConfig(fixture.tokens[TokenIndex::STABLE].address.clone()); + fixture + .env + .storage() + .persistent() + .set::(&key, &usdc_config); + let key = PoolDataKey::ResConfig(fixture.tokens[TokenIndex::XLM].address.clone()); + fixture + .env + .storage() + .persistent() + .set::(&key, &xlm_config); + let key = PoolDataKey::ResConfig(fixture.tokens[TokenIndex::WETH].address.clone()); + fixture + .env + .storage() + .persistent() + .set::(&key, &weth_config); + }); + // Create a user let samwise = Address::generate(&fixture.env); //sam will be supplying XLM and borrowing STABLE From 4823ca08ae25cb11671b84bc980f7f965472da44 Mon Sep 17 00:00:00 2001 From: markuspluna <59978114+markuspluna@users.noreply.github.com> Date: Thu, 21 Dec 2023 11:42:47 -0600 Subject: [PATCH 6/8] more test fixes --- test-suites/tests/test_liquidation.rs | 59 +++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/test-suites/tests/test_liquidation.rs b/test-suites/tests/test_liquidation.rs index f1eaf455..1f2bce1b 100644 --- a/test-suites/tests/test_liquidation.rs +++ b/test-suites/tests/test_liquidation.rs @@ -18,6 +18,42 @@ fn test_liquidations() { let frodo = fixture.users.get(0).unwrap(); let pool_fixture = &fixture.pools[0]; + //accrue interest + let requests: Vec = vec![ + &fixture.env, + Request { + request_type: 4, + address: fixture.tokens[TokenIndex::STABLE].address.clone(), + amount: 1, + }, + Request { + request_type: 5, + address: fixture.tokens[TokenIndex::STABLE].address.clone(), + amount: 1, + }, + Request { + request_type: 4, + address: fixture.tokens[TokenIndex::XLM].address.clone(), + amount: 1, + }, + Request { + request_type: 5, + address: fixture.tokens[TokenIndex::XLM].address.clone(), + amount: 1, + }, + Request { + request_type: 4, + address: fixture.tokens[TokenIndex::WETH].address.clone(), + amount: 1, + }, + Request { + request_type: 5, + address: fixture.tokens[TokenIndex::WETH].address.clone(), + amount: 1, + }, + ]; + pool_fixture.pool.submit(&frodo, &frodo, &frodo, &requests); + // Disable rate modifiers let mut usdc_config: ReserveConfig = fixture.read_reserve_config(0, TokenIndex::STABLE); usdc_config.reactivity = 0; @@ -763,6 +799,16 @@ fn test_liquidations() { .pool .submit(&samwise, &samwise, &samwise, &sam_requests); + println!( + "samwise_positions_post borrow {:?}", + sam_positions.liabilities + ); + println!("current ledger {}", fixture.env.ledger().sequence()); + println!( + "current ledger timestamp {}", + fixture.env.ledger().timestamp() + ); + // Nuke eth price more fixture.oracle.set_price_stable(&vec![ &fixture.env, @@ -823,11 +869,19 @@ fn test_liquidations() { .pool .submit(&frodo, &frodo, &frodo, &bad_debt_fill_request); // transfer bad debt to backstop + let samwise_positions_pre_bd = + pool_fixture + .pool + .submit(&samwise, &samwise, &samwise, &blank_request); + println!( + "samwise_positions_pre_bd {:?}", + samwise_positions_pre_bd.liabilities + ); pool_fixture.pool.bad_debt(&samwise); let events = fixture.env.events().all(); let event = vec![&fixture.env, events.get_unchecked(events.len() - 1)]; - let bad_debt: i128 = 92903018; + let bad_debt: i128 = 92587244; //92587244 //92903018 assert_eq!( event, vec![ @@ -873,7 +927,7 @@ fn test_liquidations() { assert_eq!(positions.liabilities.get(0).unwrap(), bad_debt); }); // check d_supply - let d_supply = 19104604033; + let d_supply = 19104301981; //19104604033 fixture.env.as_contract(&pool_fixture.pool.address, || { let key = PoolDataKey::ResData(fixture.tokens[TokenIndex::STABLE].address.clone()); let data = fixture @@ -920,7 +974,6 @@ fn test_liquidations() { }); let events = fixture.env.events().all(); let event = vec![&fixture.env, events.get_unchecked(events.len() - 2)]; - let bad_debt: i128 = 92903018; assert_eq!( event, vec![ From ace67dd4dc39d6f2e5f57e0c7906a781f7e90206 Mon Sep 17 00:00:00 2001 From: markuspluna <59978114+markuspluna@users.noreply.github.com> Date: Thu, 21 Dec 2023 12:07:30 -0600 Subject: [PATCH 7/8] Pool: reset ir_mod on ir param updates --- pool/src/pool/config.rs | 72 ++++++++++++++++++++++++++- test-suites/tests/test_liquidation.rs | 27 +++------- test-suites/tests/test_pool.rs | 10 ++-- 3 files changed, 80 insertions(+), 29 deletions(-) diff --git a/pool/src/pool/config.rs b/pool/src/pool/config.rs index 9eefd53e..4185fe54 100644 --- a/pool/src/pool/config.rs +++ b/pool/src/pool/config.rs @@ -101,12 +101,23 @@ fn initialize_reserve(e: &Env, asset: &Address, config: &ReserveConfig) -> u32 { if storage::has_res(e, asset) { // accrue and store reserve data to the ledger let pool = Pool::load(e); - let reserve = pool.load_reserve(e, asset); + let mut reserve = pool.load_reserve(e, asset); reserve.store(e); index = reserve.index; - if reserve.scalar != 10i128.pow(config.decimals) { + let reserve_config = storage::get_res_config(e, asset); + // decimals cannot change + if reserve_config.decimals != config.decimals { panic_with_error!(e, PoolError::InvalidReserveMetadata); } + // if any of the IR parameters were changed reset the IR modifier + if reserve_config.r_one != config.r_one + || reserve_config.r_two != config.r_two + || reserve_config.r_three != config.r_three + || reserve_config.util != config.util + { + reserve.ir_mod = 1_000_000_000; + } + reserve.store(e); } else { index = storage::push_res_list(e, asset); let init_data = ReserveData { @@ -646,6 +657,63 @@ mod tests { }); } + #[test] + fn test_execute_update_reserve_resets_ir_mod() { + let e = Env::default(); + e.mock_all_auths(); + e.ledger().set(LedgerInfo { + timestamp: 500, + protocol_version: 20, + sequence_number: 100, + network_id: Default::default(), + base_reserve: 10, + min_temp_entry_ttl: 10, + min_persistent_entry_ttl: 10, + max_entry_ttl: 2000000, + }); + + let pool = testutils::create_pool(&e); + let bombadil = Address::generate(&e); + + let (underlying, _) = testutils::create_token_contract(&e, &bombadil); + let (reserve_config, reserve_data) = testutils::default_reserve_meta(); + testutils::create_reserve(&e, &pool, &underlying, &reserve_config, &reserve_data); + + let new_metadata = ReserveConfig { + index: 99, + decimals: 7, + c_factor: 0_7500000, + l_factor: 0_7500000, + util: 1_0777777, + max_util: 0_9500000, + r_one: 0_0500000, + r_two: 0_7500000, + r_three: 1_5000000, + reactivity: 105, + }; + + let pool_config = PoolConfig { + oracle: Address::generate(&e), + bstop_rate: 0_100_000_000, + status: 0, + }; + e.as_contract(&pool, || { + storage::set_pool_config(&e, &pool_config); + + storage::set_queued_reserve_set( + &e, + &QueuedReserveInit { + new_config: new_metadata.clone(), + unlock_time: e.ledger().timestamp(), + }, + &underlying, + ); + execute_set_queued_reserve(&e, &underlying); + let res_data = storage::get_res_data(&e, &underlying); + assert_eq!(res_data.ir_mod, 1_000_000_000); + }); + } + #[test] fn test_validate_reserve_metadata() { let e = Env::default(); diff --git a/test-suites/tests/test_liquidation.rs b/test-suites/tests/test_liquidation.rs index 1f2bce1b..6a7ce85b 100644 --- a/test-suites/tests/test_liquidation.rs +++ b/test-suites/tests/test_liquidation.rs @@ -799,16 +799,6 @@ fn test_liquidations() { .pool .submit(&samwise, &samwise, &samwise, &sam_requests); - println!( - "samwise_positions_post borrow {:?}", - sam_positions.liabilities - ); - println!("current ledger {}", fixture.env.ledger().sequence()); - println!( - "current ledger timestamp {}", - fixture.env.ledger().timestamp() - ); - // Nuke eth price more fixture.oracle.set_price_stable(&vec![ &fixture.env, @@ -869,19 +859,16 @@ fn test_liquidations() { .pool .submit(&frodo, &frodo, &frodo, &bad_debt_fill_request); // transfer bad debt to backstop - let samwise_positions_pre_bd = - pool_fixture - .pool - .submit(&samwise, &samwise, &samwise, &blank_request); - println!( - "samwise_positions_pre_bd {:?}", - samwise_positions_pre_bd.liabilities - ); + + pool_fixture + .pool + .submit(&samwise, &samwise, &samwise, &blank_request); + pool_fixture.pool.bad_debt(&samwise); let events = fixture.env.events().all(); let event = vec![&fixture.env, events.get_unchecked(events.len() - 1)]; - let bad_debt: i128 = 92587244; //92587244 //92903018 + let bad_debt: i128 = 92903018; assert_eq!( event, vec![ @@ -927,7 +914,7 @@ fn test_liquidations() { assert_eq!(positions.liabilities.get(0).unwrap(), bad_debt); }); // check d_supply - let d_supply = 19104301981; //19104604033 + let d_supply = 19104604034; fixture.env.as_contract(&pool_fixture.pool.address, || { let key = PoolDataKey::ResData(fixture.tokens[TokenIndex::STABLE].address.clone()); let data = fixture diff --git a/test-suites/tests/test_pool.rs b/test-suites/tests/test_pool.rs index 2426c96a..61d0cc95 100644 --- a/test-suites/tests/test_pool.rs +++ b/test-suites/tests/test_pool.rs @@ -601,7 +601,7 @@ fn test_pool_config() { AuthorizedInvocation { function: AuthorizedFunction::Contract(( pool_fixture.pool.address.clone(), - Symbol::new(&fixture.env, "queue_init_reserve"), + Symbol::new(&fixture.env, "queue_set_reserve"), vec![ &fixture.env, blnd.address.to_val(), @@ -684,12 +684,8 @@ fn test_pool_config() { &fixture.env, ( pool_fixture.pool.address.clone(), - ( - Symbol::new(&fixture.env, "update_reserve"), - fixture.bombadil.clone() - ) - .into_val(&fixture.env), - blnd.address.to_val() + (Symbol::new(&fixture.env, "set_reserve"),).into_val(&fixture.env), + event_data.into_val(&fixture.env) ) ] ); From 685cd267ff1a91f9ae4f1e4cabb362fa2ab56361 Mon Sep 17 00:00:00 2001 From: markuspluna <59978114+markuspluna@users.noreply.github.com> Date: Thu, 21 Dec 2023 13:51:28 -0600 Subject: [PATCH 8/8] requested changes --- pool/src/pool/config.rs | 1 - pool/src/pool/status.rs | 48 +++++++++++++++++++++++++++++++++++++++-- pool/src/storage.rs | 14 ++++++------ 3 files changed, 53 insertions(+), 10 deletions(-) diff --git a/pool/src/pool/config.rs b/pool/src/pool/config.rs index 4185fe54..f0966f14 100644 --- a/pool/src/pool/config.rs +++ b/pool/src/pool/config.rs @@ -102,7 +102,6 @@ fn initialize_reserve(e: &Env, asset: &Address, config: &ReserveConfig) -> u32 { // accrue and store reserve data to the ledger let pool = Pool::load(e); let mut reserve = pool.load_reserve(e, asset); - reserve.store(e); index = reserve.index; let reserve_config = storage::get_res_config(e, asset); // decimals cannot change diff --git a/pool/src/pool/status.rs b/pool/src/pool/status.rs index c50e4092..8b0dd0c5 100644 --- a/pool/src/pool/status.rs +++ b/pool/src/pool/status.rs @@ -341,7 +341,7 @@ mod tests { #[test] #[should_panic(expected = "Error(Contract, #8)")] - fn test_set_pool_status_on_ice_blocks_with_too_high_q4w() { + fn test_set_pool_status_admin_on_ice_blocks_with_too_high_q4w() { let e = Env::default(); e.budget().reset_unlimited(); e.mock_all_auths_allowing_non_root_auth(); @@ -374,7 +374,7 @@ mod tests { let pool_config = PoolConfig { oracle: oracle_id, bstop_rate: 0, - status: 2, + status: 5, }; e.as_contract(&pool_id, || { storage::set_admin(&e, &bombadil); @@ -384,6 +384,50 @@ mod tests { }); } #[test] + #[should_panic(expected = "Error(Contract, #8)")] + fn test_set_pool_status_backstop_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.queue_withdrawal(&samwise, &pool_id, &40_000_0000000); + + let pool_config = PoolConfig { + oracle: oracle_id, + bstop_rate: 0, + status: 6, + }; + e.as_contract(&pool_id, || { + storage::set_admin(&e, &bombadil); + storage::set_pool_config(&e, &pool_config); + + execute_set_pool_status(&e, 3); + }); + } + #[test] fn test_set_pool_status_frozen() { let e = Env::default(); e.budget().reset_unlimited(); diff --git a/pool/src/storage.rs b/pool/src/storage.rs index 5635b51c..bbe742c5 100644 --- a/pool/src/storage.rs +++ b/pool/src/storage.rs @@ -377,10 +377,10 @@ pub fn has_res(e: &Env, asset: &Address) -> bool { pub fn get_queued_reserve_set(e: &Env, asset: &Address) -> QueuedReserveInit { let key = PoolDataKey::ResInit(asset.clone()); e.storage() - .persistent() - .extend_ttl(&key, LEDGER_THRESHOLD_SHARED, LEDGER_BUMP_SHARED); + .temporary() + .extend_ttl(&key, LEDGER_THRESHOLD_USER, LEDGER_BUMP_USER); e.storage() - .persistent() + .temporary() .get::(&key) .unwrap_optimized() } @@ -393,11 +393,11 @@ pub fn get_queued_reserve_set(e: &Env, asset: &Address) -> QueuedReserveInit { pub fn set_queued_reserve_set(e: &Env, res_init: &QueuedReserveInit, asset: &Address) { let key = PoolDataKey::ResInit(asset.clone()); e.storage() - .persistent() + .temporary() .set::(&key, res_init); e.storage() - .persistent() - .extend_ttl(&key, LEDGER_THRESHOLD_SHARED, LEDGER_BUMP_SHARED); + .temporary() + .extend_ttl(&key, LEDGER_THRESHOLD_USER, LEDGER_BUMP_USER); } /// Delete a queued reserve set @@ -409,7 +409,7 @@ pub fn set_queued_reserve_set(e: &Env, res_init: &QueuedReserveInit, asset: &Add /// If the reserve set has not been queued pub fn del_queued_reserve_set(e: &Env, asset: &Address) { let key = PoolDataKey::ResInit(asset.clone()); - e.storage().persistent().remove(&key); + e.storage().temporary().remove(&key); } /********** Reserve Data (ResData) **********/