Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Issue 156 queue reserve inits #158

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion emitter/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down
5 changes: 4 additions & 1 deletion pool-factory/src/pool_factory.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -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)
Expand Down
18 changes: 18 additions & 0 deletions pool-factory/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Copy link
Contributor

Choose a reason for hiding this comment

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

this can be fetched from the Pool WASM, to avoid duplication. The Pool Factory is already dependent on it.

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 {
Expand Down
54 changes: 49 additions & 5 deletions pool-factory/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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!(
Expand All @@ -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!(
Expand Down Expand Up @@ -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));
Expand Down
3 changes: 3 additions & 0 deletions pool/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Copy link
Contributor

Choose a reason for hiding this comment

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

A week seems like a really long time, no?

Consider a DAO doing this:

  1. Create proposal for queueing the init_reserve (2 days before voting, 3 day voting period, 2 day timelock) (7 days)
  2. Wait ~ 1 day
  3. Create another proposal for submitting the init (7 day gov period)

So its a 15 day, 2 proposal process to add a reserve to the pool, assuming they are smart enough to double dip the timelock and proposal period.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will most DAO's have a mandatory 2 day period before voting? I made initialization permissionless so I think it's important the queue time is longer than a typical DAO vote -> execution time so they can cancel initializations if a malicious one snuck in without anyone noticing

62 changes: 55 additions & 7 deletions pool/src/contract.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand Down Expand Up @@ -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
Expand All @@ -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);
Copy link
Contributor

Choose a reason for hiding this comment

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

is this necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it's potentially problematic not to have one - consider someone sneaks through a bad DAO proposal or tricks a multisig into adding a reserve - note that executing the queued initialization is not permissioned


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

Choose a reason for hiding this comment

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

we should validate the metadata on queue

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That duplicates code since we reuse the initialize function which validates metadata

fn init_reserve(e: Env, asset: Address) -> u32;

/// (Admin only) Update a reserve in the pool
///
Expand Down Expand Up @@ -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);

Expand All @@ -236,6 +259,7 @@ impl Pool for PoolContract {
&backstop_id,
&blnd_id,
&usdc_id,
&reserves,
);
}

Expand All @@ -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
}

Expand Down
1 change: 1 addition & 0 deletions pool/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub enum PoolError {
NegativeAmount = 4,
InvalidPoolInitArgs = 5,
InvalidReserveMetadata = 6,
InitNotUnlocked = 7,
// Pool State Errors (10-19)
InvalidHf = 10,
InvalidPoolStatus = 11,
Expand Down
Loading
Loading