From c42f3a06e6c7b9d14ac3e21db0a01b031b7f3d3e Mon Sep 17 00:00:00 2001 From: Kaloyan Gangov <6922910+gangov@users.noreply.github.com> Date: Wed, 15 Jan 2025 21:05:20 +0200 Subject: [PATCH] Revert "All: add constructors" --- contracts/factory/src/contract.rs | 144 ++++++---- contracts/factory/src/error.rs | 15 +- contracts/factory/src/storage.rs | 19 +- contracts/factory/src/tests.rs | 49 +++- contracts/factory/src/tests/config.rs | 31 +- contracts/factory/src/tests/setup.rs | 29 +- contracts/factory/src/utils.rs | 29 +- contracts/multihop/src/contract.rs | 34 ++- contracts/multihop/src/error.rs | 7 +- contracts/multihop/src/lib.rs | 7 - contracts/multihop/src/storage.rs | 18 +- contracts/multihop/src/tests.rs | 19 ++ contracts/multihop/src/tests/setup.rs | 61 ++-- contracts/multihop/src/tests/swap.rs | 2 +- contracts/pool/src/contract.rs | 256 +++++++++-------- contracts/pool/src/error.rs | 24 +- contracts/pool/src/storage.rs | 46 ++- contracts/pool/src/tests/config.rs | 50 ++-- contracts/pool/src/tests/setup.rs | 26 +- contracts/pool/src/tests/stake_deployment.rs | 78 +++++- contracts/pool_stable/src/contract.rs | 264 ++++++++++-------- contracts/pool_stable/src/error.rs | 35 +-- contracts/pool_stable/src/storage.rs | 46 ++- contracts/pool_stable/src/tests/setup.rs | 26 +- .../pool_stable/src/tests/stake_deployment.rs | 102 ++++++- contracts/stake/src/contract.rs | 122 ++++---- contracts/stake/src/error.rs | 27 +- contracts/stake/src/storage.rs | 16 +- contracts/stake/src/tests/bond.rs | 103 ++++--- contracts/stake/src/tests/setup.rs | 25 +- contracts/stake_rewards/src/contract.rs | 110 +++++--- contracts/stake_rewards/src/error.rs | 25 +- contracts/stake_rewards/src/storage.rs | 18 +- contracts/stake_rewards/src/tests/setup.rs | 23 +- contracts/trader/src/contract.rs | 77 ++--- contracts/trader/src/error.rs | 3 +- contracts/trader/src/storage.rs | 20 +- contracts/trader/src/tests/msgs.rs | 170 +++++++---- contracts/trader/src/tests/setup.rs | 58 ++-- contracts/vesting/src/contract.rs | 135 +++++---- contracts/vesting/src/storage.rs | 39 +-- contracts/vesting/src/tests/claim.rs | 43 +-- contracts/vesting/src/tests/instantiate.rs | 54 ++-- contracts/vesting/src/tests/minter.rs | 124 ++------ contracts/vesting/src/tests/setup.rs | 22 +- scripts/deploy.sh | 221 +++------------ 46 files changed, 1570 insertions(+), 1282 deletions(-) diff --git a/contracts/factory/src/contract.rs b/contracts/factory/src/contract.rs index 394fd8f6b..33e3ac233 100644 --- a/contracts/factory/src/contract.rs +++ b/contracts/factory/src/contract.rs @@ -2,11 +2,11 @@ use crate::{ error::ContractError, stake_contract::StakedResponse, storage::{ - get_config, get_lp_vec, get_stable_wasm_hash, save_config, save_lp_vec, - save_lp_vec_with_tuple_as_key, save_stable_wasm_hash, Asset, Config, LiquidityPoolInfo, - LpPortfolio, PairTupleKey, StakePortfolio, UserPortfolio, ADMIN, + get_config, get_lp_vec, get_stable_wasm_hash, is_initialized, save_config, save_lp_vec, + save_lp_vec_with_tuple_as_key, save_stable_wasm_hash, set_initialized, Asset, Config, + LiquidityPoolInfo, LpPortfolio, PairTupleKey, StakePortfolio, UserPortfolio, ADMIN, }, - utils::deploy_and_initialize_multihop_contract, + utils::{deploy_and_initialize_multihop_contract, deploy_lp_contract}, ConvertVec, }; use phoenix::{ @@ -18,8 +18,8 @@ use phoenix::{ utils::{LiquidityPoolInitInfo, PoolType, StakeInitInfo, TokenInitInfo}, }; use soroban_sdk::{ - contract, contractimpl, contractmeta, log, panic_with_error, vec, xdr::ToXdr, Address, Bytes, - BytesN, Env, IntoVal, String, Symbol, Val, Vec, + contract, contractimpl, contractmeta, log, panic_with_error, vec, Address, BytesN, Env, + IntoVal, String, Symbol, Val, Vec, }; // Metadata that is added on to the WASM custom section @@ -30,6 +30,19 @@ pub struct Factory; #[allow(dead_code)] pub trait FactoryTrait { + #[allow(clippy::too_many_arguments)] + fn initialize( + env: Env, + admin: Address, + multihop_wasm_hash: BytesN<32>, + lp_wasm_hash: BytesN<32>, + stable_wasm_hash: BytesN<32>, + stake_wasm_hash: BytesN<32>, + token_wasm_hash: BytesN<32>, + whitelisted_accounts: Vec
, + lp_token_decimals: u32, + ); + #[allow(clippy::too_many_arguments)] fn create_liquidity_pool( env: Env, @@ -76,6 +89,56 @@ pub trait FactoryTrait { #[contractimpl] impl FactoryTrait for Factory { + #[allow(clippy::too_many_arguments)] + fn initialize( + env: Env, + admin: Address, + multihop_wasm_hash: BytesN<32>, + lp_wasm_hash: BytesN<32>, + stable_wasm_hash: BytesN<32>, + stake_wasm_hash: BytesN<32>, + token_wasm_hash: BytesN<32>, + whitelisted_accounts: Vec
, + lp_token_decimals: u32, + ) { + if is_initialized(&env) { + log!( + &env, + "Factory: Initialize: initializing contract twice is not allowed" + ); + panic_with_error!(&env, ContractError::AlreadyInitialized); + } + + if whitelisted_accounts.is_empty() { + log!(&env, "Factory: Initialize: there must be at least one whitelisted account able to create liquidity pools."); + panic_with_error!(&env, ContractError::WhiteListeEmpty); + } + + set_initialized(&env); + + let multihop_address = + deploy_and_initialize_multihop_contract(env.clone(), admin.clone(), multihop_wasm_hash); + + save_config( + &env, + Config { + admin: admin.clone(), + multihop_address, + lp_wasm_hash, + stake_wasm_hash, + token_wasm_hash, + whitelisted_accounts, + lp_token_decimals, + }, + ); + save_stable_wasm_hash(&env, stable_wasm_hash); + + save_lp_vec(&env, Vec::new(&env)); + + env.events() + .publish(("initialize", "LP factory contract"), admin); + } + #[allow(clippy::too_many_arguments)] fn create_liquidity_pool( env: Env, @@ -112,6 +175,18 @@ impl FactoryTrait for Factory { let stake_wasm_hash = config.stake_wasm_hash; let token_wasm_hash = config.token_wasm_hash; + let pool_hash = match pool_type { + PoolType::Xyk => config.lp_wasm_hash, + PoolType::Stable => get_stable_wasm_hash(&env), + }; + + let lp_contract_address = deploy_lp_contract( + &env, + pool_hash, + &lp_init_info.token_init_info.token_a, + &lp_init_info.token_init_info.token_b, + ); + validate_bps!( lp_init_info.swap_fee_bps, lp_init_info.max_allowed_slippage_bps, @@ -122,6 +197,7 @@ impl FactoryTrait for Factory { ); let factory_addr = env.current_contract_address(); + let init_fn: Symbol = Symbol::new(&env, "initialize"); let mut init_fn_args: Vec = ( stake_wasm_hash, token_wasm_hash, @@ -142,21 +218,7 @@ impl FactoryTrait for Factory { init_fn_args.push_back(max_allowed_fee_bps.into_val(&env)); - let mut salt = Bytes::new(&env); - salt.append(&lp_init_info.token_init_info.token_a.clone().to_xdr(&env)); - salt.append(&lp_init_info.token_init_info.token_b.clone().to_xdr(&env)); - let salt = env.crypto().sha256(&salt); - - let lp_contract_address = match pool_type { - PoolType::Xyk => env - .deployer() - .with_current_contract(salt) - .deploy_v2(config.lp_wasm_hash, init_fn_args.clone()), - PoolType::Stable => env - .deployer() - .with_current_contract(salt) - .deploy_v2(get_stable_wasm_hash(&env), init_fn_args), - }; + env.invoke_contract::(&lp_contract_address, &init_fn, init_fn_args); let mut lp_vec = get_lp_vec(&env); @@ -439,46 +501,6 @@ impl FactoryTrait for Factory { #[contractimpl] impl Factory { - #[allow(clippy::too_many_arguments)] - pub fn __constructor( - env: Env, - admin: Address, - multihop_wasm_hash: BytesN<32>, - lp_wasm_hash: BytesN<32>, - stable_wasm_hash: BytesN<32>, - stake_wasm_hash: BytesN<32>, - token_wasm_hash: BytesN<32>, - whitelisted_accounts: Vec
, - lp_token_decimals: u32, - ) { - if whitelisted_accounts.is_empty() { - log!(&env, "Factory: Initialize: there must be at least one whitelisted account able to create liquidity pools."); - panic_with_error!(&env, ContractError::WhiteListeEmpty); - } - - let multihop_address = - deploy_and_initialize_multihop_contract(env.clone(), admin.clone(), multihop_wasm_hash); - - save_config( - &env, - Config { - admin: admin.clone(), - multihop_address, - lp_wasm_hash, - stake_wasm_hash, - token_wasm_hash, - whitelisted_accounts, - lp_token_decimals, - }, - ); - save_stable_wasm_hash(&env, stable_wasm_hash); - - save_lp_vec(&env, Vec::new(&env)); - - env.events() - .publish(("initialize", "LP factory contract"), admin); - } - #[allow(dead_code)] pub fn update(env: Env, new_wasm_hash: BytesN<32>, new_stable_pool_hash: BytesN<32>) { let admin = get_config(&env).admin; diff --git a/contracts/factory/src/error.rs b/contracts/factory/src/error.rs index 622f044b0..ced4476ac 100644 --- a/contracts/factory/src/error.rs +++ b/contracts/factory/src/error.rs @@ -4,11 +4,12 @@ use soroban_sdk::contracterror; #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] #[repr(u32)] pub enum ContractError { - WhiteListeEmpty = 1, - NotAuthorized = 2, - LiquidityPoolNotFound = 3, - TokenABiggerThanTokenB = 4, - MinStakeInvalid = 5, - MinRewardInvalid = 6, - AdminNotSet = 7, + AlreadyInitialized = 1, + WhiteListeEmpty = 2, + NotAuthorized = 3, + LiquidityPoolNotFound = 4, + TokenABiggerThanTokenB = 5, + MinStakeInvalid = 6, + MinRewardInvalid = 7, + AdminNotSet = 8, } diff --git a/contracts/factory/src/storage.rs b/contracts/factory/src/storage.rs index 3d1eea69f..a82882a77 100644 --- a/contracts/factory/src/storage.rs +++ b/contracts/factory/src/storage.rs @@ -16,7 +16,7 @@ pub const ADMIN: Symbol = symbol_short!("ADMIN"); pub enum DataKey { Config = 1, LpVec = 2, - Initialized = 3, // deprecated, do not remove for now + Initialized = 3, } #[derive(Clone)] @@ -233,3 +233,20 @@ pub fn save_lp_vec_with_tuple_as_key( PERSISTENT_BUMP_AMOUNT, ); } + +pub fn is_initialized(e: &Env) -> bool { + e.storage() + .persistent() + .get(&DataKey::Initialized) + .unwrap_or(false) +} + +pub fn set_initialized(e: &Env) { + e.storage().persistent().set(&DataKey::Initialized, &true); + + e.storage().persistent().extend_ttl( + &DataKey::Initialized, + PERSISTENT_LIFETIME_THRESHOLD, + PERSISTENT_BUMP_AMOUNT, + ); +} diff --git a/contracts/factory/src/tests.rs b/contracts/factory/src/tests.rs index 035cf4aec..48f5751a8 100644 --- a/contracts/factory/src/tests.rs +++ b/contracts/factory/src/tests.rs @@ -1,3 +1,50 @@ +use soroban_sdk::{testutils::Address as _, vec, Address, Env}; + +use self::setup::{ + deploy_factory_contract, install_lp_contract, install_multihop_wasm, install_stable_lp, + install_stake_wasm, install_token_wasm, +}; + mod config; -mod queries; mod setup; + +mod queries; +#[test] +#[should_panic(expected = "Factory: Initialize: initializing contract twice is not allowed")] +fn test_deploy_factory_twice_should_fail() { + let env = Env::default(); + env.mock_all_auths(); + env.cost_estimate().budget().reset_unlimited(); + + let admin = Address::generate(&env); + + let auth_user = Address::generate(&env); + let multihop_wasm_hash = install_multihop_wasm(&env); + let lp_wasm_hash = install_lp_contract(&env); + let stable_wasm_hash = install_stable_lp(&env); + let stake_wasm_hash = install_stake_wasm(&env); + let token_wasm_hash = install_token_wasm(&env); + + let factory = deploy_factory_contract(&env, admin.clone()); + + factory.initialize( + &admin, + &multihop_wasm_hash, + &lp_wasm_hash, + &stable_wasm_hash, + &stake_wasm_hash, + &token_wasm_hash, + &vec![&env, auth_user.clone()], + &10u32, + ); + factory.initialize( + &admin, + &multihop_wasm_hash, + &lp_wasm_hash, + &stable_wasm_hash, + &stake_wasm_hash, + &token_wasm_hash, + &vec![&env, auth_user.clone()], + &10u32, + ); +} diff --git a/contracts/factory/src/tests/config.rs b/contracts/factory/src/tests/config.rs index 573269793..06089bf7d 100644 --- a/contracts/factory/src/tests/config.rs +++ b/contracts/factory/src/tests/config.rs @@ -245,29 +245,24 @@ fn factory_fails_to_init_lp_when_no_whitelisted_accounts() { env.mock_all_auths(); env.cost_estimate().budget().reset_unlimited(); + let factory = FactoryClient::new(&env, &env.register(Factory, ())); let multihop_wasm_hash = install_multihop_wasm(&env); - let whitelisted_accounts: soroban_sdk::Vec
= vec![&env]; + let whitelisted_accounts = vec![&env]; let lp_wasm_hash = install_lp_contract(&env); let stable_wasm_hash = install_stable_lp(&env); let stake_wasm_hash = install_stake_wasm(&env); let token_wasm_hash = install_token_wasm(&env); - let _ = FactoryClient::new( - &env, - &env.register( - Factory, - ( - &admin, - &multihop_wasm_hash, - &lp_wasm_hash, - &stable_wasm_hash, - &stake_wasm_hash, - &token_wasm_hash, - whitelisted_accounts, - &10u32, - ), - ), + factory.initialize( + &admin, + &multihop_wasm_hash, + &lp_wasm_hash, + &stable_wasm_hash, + &stake_wasm_hash, + &token_wasm_hash, + &whitelisted_accounts, + &10u32, ); } @@ -483,9 +478,9 @@ fn factory_create_xyk_pool_with_amp_parameter_should_still_succeed() { &String::from_str(&env, "Pool Stable"), &String::from_str(&env, "EUROC/USDC"), &PoolType::Xyk, - &Some(10u64), + &Some(10), &100i64, - &1_000i64, + &1_000, ); let lp_contract_addr = factory.query_pools().get(0).unwrap(); diff --git a/contracts/factory/src/tests/setup.rs b/contracts/factory/src/tests/setup.rs index ba0d9641c..9b8f6f9cc 100644 --- a/contracts/factory/src/tests/setup.rs +++ b/contracts/factory/src/tests/setup.rs @@ -3,7 +3,7 @@ use crate::{ token_contract, }; use phoenix::utils::{LiquidityPoolInitInfo, StakeInitInfo, TokenInitInfo}; -use soroban_sdk::{testutils::Address as _, vec, Address, BytesN, Env, String, Vec}; +use soroban_sdk::{testutils::Address as _, vec, Address, BytesN, Env, String}; pub const ONE_DAY: u64 = 86400; const TOKEN_WASM: &[u8] = include_bytes!("../../../../target/wasm32-unknown-unknown/release/soroban_token_contract.wasm"); @@ -62,29 +62,24 @@ pub fn deploy_factory_contract<'a>( admin: impl Into>, ) -> FactoryClient<'a> { let admin = admin.into().unwrap_or(Address::generate(env)); + let factory = FactoryClient::new(env, &env.register(Factory, ())); let multihop_wasm_hash = install_multihop_wasm(env); - let whitelisted_accounts: Vec
= vec![env, admin.clone()]; + let whitelisted_accounts = vec![env, admin.clone()]; let lp_wasm_hash = install_lp_contract(env); let stable_wasm_hash = install_stable_lp(env); let stake_wasm_hash = install_stake_wasm(env); let token_wasm_hash = install_token_wasm(env); - let factory = FactoryClient::new( - env, - &env.register( - Factory, - ( - &admin, - &multihop_wasm_hash, - &lp_wasm_hash, - &stable_wasm_hash, - &stake_wasm_hash, - &token_wasm_hash, - whitelisted_accounts, - &10u32, - ), - ), + factory.initialize( + &admin, + &multihop_wasm_hash, + &lp_wasm_hash, + &stable_wasm_hash, + &stake_wasm_hash, + &token_wasm_hash, + &whitelisted_accounts, + &10u32, ); factory diff --git a/contracts/factory/src/utils.rs b/contracts/factory/src/utils.rs index 94a6eb0d8..a9fa48d6c 100644 --- a/contracts/factory/src/utils.rs +++ b/contracts/factory/src/utils.rs @@ -1,4 +1,20 @@ -use soroban_sdk::{xdr::ToXdr, Address, Bytes, BytesN, Env}; +use soroban_sdk::{xdr::ToXdr, Address, Bytes, BytesN, Env, IntoVal, Symbol, Val, Vec}; + +pub fn deploy_lp_contract( + env: &Env, + wasm_hash: BytesN<32>, + token_a: &Address, + token_b: &Address, +) -> Address { + let mut salt = Bytes::new(env); + salt.append(&token_a.to_xdr(env)); + salt.append(&token_b.to_xdr(env)); + let salt = env.crypto().sha256(&salt); + + env.deployer() + .with_current_contract(salt) + .deploy_v2(wasm_hash, ()) +} pub fn deploy_and_initialize_multihop_contract( env: Env, @@ -9,7 +25,14 @@ pub fn deploy_and_initialize_multihop_contract( salt.append(&admin.clone().to_xdr(&env)); let salt = env.crypto().sha256(&salt); - env.deployer() + let multihop_address = env + .deployer() .with_current_contract(salt) - .deploy_v2(multihop_wasm_hash, (admin, env.current_contract_address())) + .deploy_v2(multihop_wasm_hash, ()); + + let init_fn = Symbol::new(&env, "initialize"); + let init_args: Vec = (admin, env.current_contract_address()).into_val(&env); + env.invoke_contract::(&multihop_address, &init_fn, init_args); + + multihop_address } diff --git a/contracts/multihop/src/contract.rs b/contracts/multihop/src/contract.rs index 8d4afd5bc..8de901cbc 100644 --- a/contracts/multihop/src/contract.rs +++ b/contracts/multihop/src/contract.rs @@ -8,8 +8,8 @@ use crate::factory_contract::PoolType; // FIXM: Disable Referral struct // use crate::lp_contract::Referral; use crate::storage::{ - get_admin_old, get_factory, save_admin_old, save_factory, SimulateReverseSwapResponse, - SimulateSwapResponse, Swap, ADMIN, + get_admin_old, get_factory, is_initialized, save_admin_old, save_factory, set_initialized, + SimulateReverseSwapResponse, SimulateSwapResponse, Swap, ADMIN, }; use crate::utils::{verify_reverse_swap, verify_swap}; use crate::{factory_contract, stable_pool, token_contract, xyk_pool}; @@ -25,6 +25,8 @@ pub struct Multihop; #[allow(dead_code)] pub trait MultihopTrait { + fn initialize(env: Env, admin: Address, factory: Address); + #[allow(clippy::too_many_arguments)] fn swap( env: Env, @@ -58,6 +60,25 @@ pub trait MultihopTrait { #[contractimpl] impl MultihopTrait for Multihop { + fn initialize(env: Env, admin: Address, factory: Address) { + if is_initialized(&env) { + log!( + &env, + "Multihop: Initialize: initializing contract twice is not allowed" + ); + panic_with_error!(&env, ContractError::AlreadyInitialized); + } + + set_initialized(&env); + + save_admin_old(&env, &admin); + + save_factory(&env, factory); + + env.events() + .publish(("initialize", "Multihop factory with admin: "), admin); + } + #[allow(clippy::too_many_arguments)] fn swap( env: Env, @@ -277,15 +298,6 @@ impl MultihopTrait for Multihop { #[contractimpl] impl Multihop { - pub fn __constructor(env: Env, admin: Address, factory: Address) { - save_admin_old(&env, &admin); - - save_factory(&env, factory); - - env.events() - .publish(("initialize", "Multihop factory with admin: "), admin); - } - #[allow(dead_code)] pub fn update(env: Env, new_wasm_hash: BytesN<32>) { let admin = get_admin_old(&env); diff --git a/contracts/multihop/src/error.rs b/contracts/multihop/src/error.rs index 2c4e07200..c74c4b9b3 100644 --- a/contracts/multihop/src/error.rs +++ b/contracts/multihop/src/error.rs @@ -4,7 +4,8 @@ use soroban_sdk::contracterror; #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] #[repr(u32)] pub enum ContractError { - OperationsEmpty = 1, - IncorrectAssetSwap = 2, - AdminNotSet = 3, + AlreadyInitialized = 1, + OperationsEmpty = 2, + IncorrectAssetSwap = 3, + AdminNotSet = 4, } diff --git a/contracts/multihop/src/lib.rs b/contracts/multihop/src/lib.rs index 9d7fc24e4..af8906b09 100644 --- a/contracts/multihop/src/lib.rs +++ b/contracts/multihop/src/lib.rs @@ -21,13 +21,6 @@ pub mod stable_pool { ); } -#[allow(clippy::too_many_arguments)] -pub mod stake { - soroban_sdk::contractimport!( - file = "../../target/wasm32-unknown-unknown/release/phoenix_stake.wasm" - ); -} - #[allow(clippy::too_many_arguments)] pub mod factory_contract { soroban_sdk::contractimport!( diff --git a/contracts/multihop/src/storage.rs b/contracts/multihop/src/storage.rs index 11f48233b..bf8891535 100644 --- a/contracts/multihop/src/storage.rs +++ b/contracts/multihop/src/storage.rs @@ -31,7 +31,7 @@ pub enum DataKey { PairKey(Pair), FactoryKey, Admin, - Initialized, // deprecated, do not remove for now + Initialized, } #[contracttype] @@ -137,3 +137,19 @@ pub fn _get_admin(env: &Env) -> Address { admin } + +pub fn is_initialized(e: &Env) -> bool { + e.storage() + .persistent() + .get(&DataKey::Initialized) + .unwrap_or(false) +} + +pub fn set_initialized(e: &Env) { + e.storage().persistent().set(&DataKey::Initialized, &true); + e.storage().persistent().extend_ttl( + &DataKey::Initialized, + PERSISTENT_LIFETIME_THRESHOLD, + PERSISTENT_BUMP_AMOUNT, + ); +} diff --git a/contracts/multihop/src/tests.rs b/contracts/multihop/src/tests.rs index 8119c4f6d..51ca22e51 100644 --- a/contracts/multihop/src/tests.rs +++ b/contracts/multihop/src/tests.rs @@ -1,3 +1,22 @@ +use crate::contract::{Multihop, MultihopClient}; +use crate::tests::setup::deploy_factory_contract; +use soroban_sdk::{testutils::Address as _, Address, Env}; + mod query; mod setup; mod swap; + +#[test] +#[should_panic(expected = "Multihop: Initialize: initializing contract twice is not allowed")] +fn test_deploy_multihop_twice_should_fail() { + let env = Env::default(); + env.mock_all_auths(); + env.cost_estimate().budget().reset_unlimited(); + + let admin = Address::generate(&env); + + let multihop = MultihopClient::new(&env, &env.register(Multihop, ())); + let factory = deploy_factory_contract(&env, admin.clone()); + multihop.initialize(&admin, &factory); + multihop.initialize(&admin, &factory); +} diff --git a/contracts/multihop/src/tests/setup.rs b/contracts/multihop/src/tests/setup.rs index 6a34ac7e6..02225e9b7 100644 --- a/contracts/multihop/src/tests/setup.rs +++ b/contracts/multihop/src/tests/setup.rs @@ -1,16 +1,12 @@ use crate::contract::{Multihop, MultihopClient}; use crate::factory_contract::{LiquidityPoolInitInfo, PoolType, StakeInitInfo, TokenInitInfo}; -use crate::{factory_contract, stable_pool, stake, token_contract, xyk_pool}; +use crate::{factory_contract, stable_pool, token_contract, xyk_pool}; use soroban_sdk::{ testutils::{arbitrary::std, Address as _}, - Address, BytesN, Env, + Address, Bytes, BytesN, Env, }; -use soroban_sdk::{vec, String, Vec}; - -const FACTORY_WASM: &[u8] = - include_bytes!("../../../../target/wasm32-unknown-unknown/release/phoenix_factory.wasm"); - +use soroban_sdk::{vec, String}; pub fn create_token_contract_with_metadata<'a>( env: &Env, admin: &Address, @@ -49,7 +45,10 @@ pub fn deploy_token_contract<'a>(env: &Env, admin: &Address) -> token_contract:: #[allow(clippy::too_many_arguments)] pub fn install_stake_wasm(env: &Env) -> BytesN<32> { - env.deployer().upload_contract_wasm(stake::WASM) + soroban_sdk::contractimport!( + file = "../../target/wasm32-unknown-unknown/release/phoenix_stake.wasm" + ); + env.deployer().upload_contract_wasm(WASM) } #[allow(clippy::too_many_arguments)] @@ -60,6 +59,16 @@ pub fn install_multihop_wasm(env: &Env) -> BytesN<32> { env.deployer().upload_contract_wasm(WASM) } +pub fn deploy_factory_contract(e: &Env, admin: Address) -> Address { + let factory_wasm = e.deployer().upload_contract_wasm(factory_contract::WASM); + let salt = Bytes::new(e); + let salt = e.crypto().sha256(&salt); + + e.deployer() + .with_address(admin, salt) + .deploy_v2(factory_wasm, ()) +} + pub fn deploy_multihop_contract<'a>( env: &Env, admin: impl Into>, @@ -67,8 +76,9 @@ pub fn deploy_multihop_contract<'a>( ) -> MultihopClient<'a> { let admin = admin.into().unwrap_or(Address::generate(env)); - let multihop = MultihopClient::new(env, &env.register(Multihop, (&admin, factory))); + let multihop = MultihopClient::new(env, &env.register(Multihop, ())); + multihop.initialize(&admin, factory); multihop } @@ -83,32 +93,27 @@ pub fn deploy_and_mint_tokens<'a>( } pub fn deploy_and_initialize_factory(env: &Env, admin: Address) -> factory_contract::Client { + let factory_addr = deploy_factory_contract(env, admin.clone()); + let factory_client = factory_contract::Client::new(env, &factory_addr); let multihop_wasm_hash = install_multihop_wasm(env); + let whitelisted_accounts = vec![env, admin.clone()]; + let lp_wasm_hash = install_lp_contract(env); let stable_wasm_hash = install_stable_lp_contract(env); let stake_wasm_hash = install_stake_wasm(env); let token_wasm_hash = install_token_wasm(env); - let whitelisted_accounts: Vec
= vec![env, admin.clone()]; - - let factory = factory_contract::Client::new( - env, - &env.register( - FACTORY_WASM, - ( - &admin, - &multihop_wasm_hash, - &lp_wasm_hash, - &stable_wasm_hash, - &stake_wasm_hash, - &token_wasm_hash, - whitelisted_accounts, - 10u32, - ), - ), + factory_client.initialize( + &admin.clone(), + &multihop_wasm_hash, + &lp_wasm_hash, + &stable_wasm_hash, + &stake_wasm_hash, + &token_wasm_hash, + &whitelisted_accounts, + &10u32, ); - - factory + factory_client } #[allow(clippy::too_many_arguments)] diff --git a/contracts/multihop/src/tests/swap.rs b/contracts/multihop/src/tests/swap.rs index 313f5071f..3234b6a0a 100644 --- a/contracts/multihop/src/tests/swap.rs +++ b/contracts/multihop/src/tests/swap.rs @@ -726,7 +726,7 @@ fn test_v_phx_vul_013_add_belief_price_for_every_swap() { } #[test] -#[should_panic(expected = "Error(Contract, #20)")] +#[should_panic(expected = "Error(Contract, #21)")] fn test_swap_with_ask_asset_min_amount() { let env = Env::default(); let admin = Address::generate(&env); diff --git a/contracts/pool/src/contract.rs b/contracts/pool/src/contract.rs index 75cb56275..1f54580c2 100644 --- a/contracts/pool/src/contract.rs +++ b/contracts/pool/src/contract.rs @@ -6,9 +6,10 @@ use num_integer::Roots; use crate::{ error::ContractError, + stake_contract, storage::{ get_config, get_default_slippage_bps, save_config, save_default_slippage_bps, - utils::{self, get_admin_old}, + utils::{self, get_admin_old, is_initialized, set_initialized}, Asset, ComputeSwap, Config, LiquidityPoolInfo, PairType, PoolResponse, SimulateReverseSwapResponse, SimulateSwapResponse, ADMIN, }, @@ -35,6 +36,21 @@ pub struct LiquidityPool; #[allow(dead_code)] pub trait LiquidityPoolTrait { + // Sets the token contract addresses for this pool + // token_wasm_hash is the WASM hash of the deployed token contract for the pool share token + #[allow(clippy::too_many_arguments)] + fn initialize( + env: Env, + stake_wasm_hash: BytesN<32>, + token_wasm_hash: BytesN<32>, + lp_init_info: LiquidityPoolInitInfo, + factory_addr: Address, + share_token_name: String, + share_token_symbol: String, + default_slippage_bps: i64, + max_allowed_fee_bps: i64, + ); + // Deposits token_a and token_b. Also mints pool shares for the "to" Identifier. The amount minted // is determined based on the difference between the reserves stored by this contract, and // the actual balance of token_a and token_b for this contract. @@ -132,6 +148,130 @@ pub trait LiquidityPoolTrait { #[contractimpl] impl LiquidityPoolTrait for LiquidityPool { + #[allow(clippy::too_many_arguments)] + fn initialize( + env: Env, + stake_wasm_hash: BytesN<32>, + token_wasm_hash: BytesN<32>, + lp_init_info: LiquidityPoolInitInfo, + factory_addr: Address, + share_token_name: String, + share_token_symbol: String, + default_slippage_bps: i64, + max_allowed_fee_bps: i64, + ) { + if is_initialized(&env) { + log!( + &env, + "Pool: Initialize: initializing contract twice is not allowed" + ); + panic_with_error!(&env, ContractError::AlreadyInitialized); + } + + let admin = lp_init_info.admin; + let swap_fee_bps = lp_init_info.swap_fee_bps; + let fee_recipient = lp_init_info.fee_recipient; + let max_allowed_slippage_bps = lp_init_info.max_allowed_slippage_bps; + let max_allowed_spread_bps = lp_init_info.max_allowed_spread_bps; + let max_referral_bps = lp_init_info.max_referral_bps; + let token_init_info = lp_init_info.token_init_info; + let stake_init_info = lp_init_info.stake_init_info; + + validate_bps!( + swap_fee_bps, + max_allowed_slippage_bps, + max_allowed_spread_bps, + max_referral_bps, + default_slippage_bps, + max_allowed_fee_bps + ); + + // if the swap_fee_bps is above the threshold, we throw an error + if swap_fee_bps > max_allowed_fee_bps { + log!( + &env, + "Pool: Initialize: swap fee is higher than the maximum allowed!" + ); + panic_with_error!(&env, ContractError::SwapFeeBpsOverLimit); + } + + set_initialized(&env); + + // Token info + let token_a = token_init_info.token_a; + let token_b = token_init_info.token_b; + // Stake info + let min_bond = stake_init_info.min_bond; + let min_reward = stake_init_info.min_reward; + let manager = stake_init_info.manager; + + // Token order validation to make sure only one instance of a pool can exist + if token_a >= token_b { + log!( + &env, + "Pool: Initialize: First token must be alphabetically smaller than second token" + ); + panic_with_error!(&env, ContractError::TokenABiggerThanTokenB); + } + + let precision1 = token_contract::Client::new(&env, &token_a).decimals(); + let precision2 = token_contract::Client::new(&env, &token_b).decimals(); + let max_precision_decimals: u32 = if precision1 > precision2 { + precision1 + } else { + precision2 + }; + + // deploy and initialize token contract + let share_token_address = utils::deploy_token_contract( + &env, + token_wasm_hash.clone(), + &token_a, + &token_b, + env.current_contract_address(), + max_precision_decimals, + share_token_name, + share_token_symbol, + ); + + let stake_contract_address = utils::deploy_stake_contract(&env, stake_wasm_hash); + stake_contract::Client::new(&env, &stake_contract_address).initialize( + &admin, + &share_token_address, + &min_bond, + &min_reward, + &manager, + &factory_addr, + &stake_init_info.max_complexity, + ); + + let config = Config { + token_a: token_a.clone(), + token_b: token_b.clone(), + share_token: share_token_address, + stake_contract: stake_contract_address, + pool_type: PairType::Xyk, + total_fee_bps: swap_fee_bps, + fee_recipient, + max_allowed_slippage_bps, + max_allowed_spread_bps, + max_referral_bps, + }; + + save_config(&env, config); + save_default_slippage_bps(&env, default_slippage_bps); + + utils::save_admin_old(&env, admin); + utils::save_total_shares(&env, 0); + utils::save_pool_balance_a(&env, 0); + utils::save_pool_balance_b(&env, 0); + + env.events() + .publish(("initialize", "XYK LP token_a"), token_a); + env.events() + .publish(("initialize", "XYK LP token_b"), token_b); + } + #[allow(clippy::too_many_arguments)] fn provide_liquidity( env: Env, @@ -650,120 +790,6 @@ impl LiquidityPoolTrait for LiquidityPool { #[contractimpl] impl LiquidityPool { - #[allow(clippy::too_many_arguments)] - pub fn __constructor( - env: Env, - stake_wasm_hash: BytesN<32>, - token_wasm_hash: BytesN<32>, - lp_init_info: LiquidityPoolInitInfo, - factory_addr: Address, - share_token_name: String, - share_token_symbol: String, - default_slippage_bps: i64, - max_allowed_fee_bps: i64, - ) { - let admin = lp_init_info.admin; - let swap_fee_bps = lp_init_info.swap_fee_bps; - let fee_recipient = lp_init_info.fee_recipient; - let max_allowed_slippage_bps = lp_init_info.max_allowed_slippage_bps; - let max_allowed_spread_bps = lp_init_info.max_allowed_spread_bps; - let max_referral_bps = lp_init_info.max_referral_bps; - let token_init_info = lp_init_info.token_init_info; - let stake_init_info = lp_init_info.stake_init_info; - - validate_bps!( - swap_fee_bps, - max_allowed_slippage_bps, - max_allowed_spread_bps, - max_referral_bps, - default_slippage_bps, - max_allowed_fee_bps - ); - - // if the swap_fee_bps is above the threshold, we throw an error - if swap_fee_bps > max_allowed_fee_bps { - log!( - &env, - "Pool: Initialize: swap fee is higher than the maximum allowed!" - ); - panic_with_error!(&env, ContractError::SwapFeeBpsOverLimit); - } - - // Token info - let token_a = token_init_info.token_a; - let token_b = token_init_info.token_b; - // Stake info - let min_bond = stake_init_info.min_bond; - let min_reward = stake_init_info.min_reward; - let manager = stake_init_info.manager; - - // Token order validation to make sure only one instance of a pool can exist - if token_a >= token_b { - log!( - &env, - "Pool: Initialize: First token must be alphabetically smaller than second token" - ); - panic_with_error!(&env, ContractError::TokenABiggerThanTokenB); - } - - let precision1 = token_contract::Client::new(&env, &token_a).decimals(); - let precision2 = token_contract::Client::new(&env, &token_b).decimals(); - let max_precision_decimals: u32 = if precision1 > precision2 { - precision1 - } else { - precision2 - }; - - // deploy and initialize token contract - let share_token_address = utils::deploy_token_contract( - &env, - token_wasm_hash.clone(), - &token_a, - &token_b, - env.current_contract_address(), - max_precision_decimals, - share_token_name, - share_token_symbol, - ); - - let stake_contract_address = utils::deploy_stake_contract( - &env, - stake_wasm_hash, - &admin, - &share_token_address, - min_bond, - min_reward, - &manager, - &factory_addr, - stake_init_info.max_complexity, - ); - - let config = Config { - token_a: token_a.clone(), - token_b: token_b.clone(), - share_token: share_token_address, - stake_contract: stake_contract_address, - pool_type: PairType::Xyk, - total_fee_bps: swap_fee_bps, - fee_recipient, - max_allowed_slippage_bps, - max_allowed_spread_bps, - max_referral_bps, - }; - - save_config(&env, config); - save_default_slippage_bps(&env, default_slippage_bps); - - utils::save_admin_old(&env, admin); - utils::save_total_shares(&env, 0); - utils::save_pool_balance_a(&env, 0); - utils::save_pool_balance_b(&env, 0); - - env.events() - .publish(("initialize", "XYK LP token_a"), token_a); - env.events() - .publish(("initialize", "XYK LP token_b"), token_b); - } #[allow(dead_code)] pub fn update(env: Env, new_wasm_hash: BytesN<32>) { let admin = get_admin_old(&env); diff --git a/contracts/pool/src/error.rs b/contracts/pool/src/error.rs index e2c05d741..e16cb4761 100644 --- a/contracts/pool/src/error.rs +++ b/contracts/pool/src/error.rs @@ -23,15 +23,17 @@ pub enum ContractError { DesiredAmountsBelowOrEqualZero = 14, MinAmountsBelowZero = 15, AssetNotInPool = 16, - TokenABiggerThanTokenB = 17, - InvalidBps = 18, - SlippageInvalid = 19, - SwapMinReceivedBiggerThanReturn = 20, - TransactionAfterTimestampDeadline = 21, - CannotConvertU256ToI128 = 22, - UserDeclinesPoolFee = 23, - SwapFeeBpsOverLimit = 24, - NotEnoughSharesToBeMinted = 25, - NotEnoughLiquidityProvided = 26, - AdminNotSet = 27, + AlreadyInitialized = 17, + TokenABiggerThanTokenB = 18, + InvalidBps = 19, + SlippageInvalid = 20, + + SwapMinReceivedBiggerThanReturn = 21, + TransactionAfterTimestampDeadline = 22, + CannotConvertU256ToI128 = 23, + UserDeclinesPoolFee = 24, + SwapFeeBpsOverLimit = 25, + NotEnoughSharesToBeMinted = 26, + NotEnoughLiquidityProvided = 27, + AdminNotSet = 28, } diff --git a/contracts/pool/src/storage.rs b/contracts/pool/src/storage.rs index 06e33198d..a8062dc4b 100644 --- a/contracts/pool/src/storage.rs +++ b/contracts/pool/src/storage.rs @@ -16,7 +16,7 @@ pub enum DataKey { ReserveA = 1, ReserveB = 2, Admin = 3, - Initialized = 4, // deprecated, do not remove for now + Initialized = 4, } impl TryFromVal for Val { @@ -207,33 +207,13 @@ pub mod utils { .deploy_v2(token_wasm_hash, (admin, decimals, name, symbol)) } - #[allow(clippy::too_many_arguments)] - pub fn deploy_stake_contract( - e: &Env, - stake_wasm_hash: BytesN<32>, - admin: &Address, - share_token_address: &Address, - min_bond: i128, - min_reward: i128, - manager: &Address, - factory_addr: &Address, - max_complexity: u32, - ) -> Address { + pub fn deploy_stake_contract(e: &Env, stake_wasm_hash: BytesN<32>) -> Address { let salt = Bytes::new(e); let salt = e.crypto().sha256(&salt); - e.deployer().with_current_contract(salt).deploy_v2( - stake_wasm_hash, - ( - admin, - share_token_address, - min_bond, - min_reward, - manager, - factory_addr, - max_complexity, - ), - ) + e.deployer() + .with_current_contract(salt) + .deploy_v2(stake_wasm_hash, ()) } pub fn save_admin_old(e: &Env, address: Address) { @@ -476,6 +456,22 @@ pub mod utils { (amount_a, amount_b) } + + pub fn is_initialized(e: &Env) -> bool { + e.storage() + .persistent() + .get(&DataKey::Initialized) + .unwrap_or(false) + } + + pub fn set_initialized(e: &Env) { + e.storage().persistent().set(&DataKey::Initialized, &true); + e.storage().persistent().extend_ttl( + &DataKey::Initialized, + PERSISTENT_LIFETIME_THRESHOLD, + PERSISTENT_BUMP_AMOUNT, + ); + } } #[cfg(test)] diff --git a/contracts/pool/src/tests/config.rs b/contracts/pool/src/tests/config.rs index a9b032926..cfdb3bf4a 100644 --- a/contracts/pool/src/tests/config.rs +++ b/contracts/pool/src/tests/config.rs @@ -27,6 +27,7 @@ fn test_initialize_with_bigger_first_token_should_fail() { std::mem::swap(&mut token1, &mut token2); } + let pool = LiquidityPoolClient::new(&env, &env.register(LiquidityPool, ())); let fee_recipient = Address::generate(&env); let token_init_info = TokenInitInfo { @@ -54,21 +55,15 @@ fn test_initialize_with_bigger_first_token_should_fail() { stake_init_info, }; - let _ = LiquidityPoolClient::new( - &env, - &env.register( - LiquidityPool, - ( - &stake_wasm_hash, - &token_wasm_hash, - lp_init_info, - &Address::generate(&env), - String::from_str(&env, "Pool"), - String::from_str(&env, "PHOBTC"), - &100i64, - &1_000i64, - ), - ), + pool.initialize( + &stake_wasm_hash, + &token_wasm_hash, + &lp_init_info, + &Address::generate(&env), + &String::from_str(&env, "Pool"), + &String::from_str(&env, "PHOBTC"), + &100i64, + &1_000, ); } @@ -458,6 +453,7 @@ fn test_initialize_with_maximum_allowed_swap_fee_bps_over_the_cap_should_fail() std::mem::swap(&mut token1, &mut token2); } + let pool = LiquidityPoolClient::new(&env, &env.register(LiquidityPool, ())); let fee_recipient = Address::generate(&env); let token_init_info = TokenInitInfo { @@ -485,20 +481,14 @@ fn test_initialize_with_maximum_allowed_swap_fee_bps_over_the_cap_should_fail() stake_init_info, }; - let _ = LiquidityPoolClient::new( - &env, - &env.register( - LiquidityPool, - ( - &stake_wasm_hash, - &token_wasm_hash, - lp_init_info, - &Address::generate(&env), - String::from_str(&env, "Pool"), - String::from_str(&env, "PHOBTC"), - &100i64, - &1_000i64, - ), - ), + pool.initialize( + &stake_wasm_hash, + &token_wasm_hash, + &lp_init_info, + &Address::generate(&env), + &String::from_str(&env, "Pool"), + &String::from_str(&env, "PHOBTC"), + &100i64, + &1_000, ); } diff --git a/contracts/pool/src/tests/setup.rs b/contracts/pool/src/tests/setup.rs index de41efc08..2e5faac0e 100644 --- a/contracts/pool/src/tests/setup.rs +++ b/contracts/pool/src/tests/setup.rs @@ -51,6 +51,7 @@ pub fn deploy_liquidity_pool_contract<'a>( stake_owner: Address, ) -> LiquidityPoolClient<'a> { let admin = admin.into().unwrap_or(Address::generate(env)); + let pool = LiquidityPoolClient::new(env, &env.register(LiquidityPool, ())); let fee_recipient = fee_recipient .into() .unwrap_or_else(|| Address::generate(env)); @@ -80,22 +81,15 @@ pub fn deploy_liquidity_pool_contract<'a>( stake_init_info, }; - let pool = LiquidityPoolClient::new( - env, - &env.register( - LiquidityPool, - ( - &stake_wasm_hash, - &token_wasm_hash, - lp_init_info, - &stake_owner, - String::from_str(env, "Pool"), - String::from_str(env, "PHOBTC"), - &100i64, - &1_000i64, - ), - ), + pool.initialize( + &stake_wasm_hash, + &token_wasm_hash, + &lp_init_info, + &stake_owner, + &String::from_str(env, "Pool"), + &String::from_str(env, "PHOBTC"), + &100i64, + &1_000, ); - pool } diff --git a/contracts/pool/src/tests/stake_deployment.rs b/contracts/pool/src/tests/stake_deployment.rs index 8dae9cf5d..ba0d302b2 100644 --- a/contracts/pool/src/tests/stake_deployment.rs +++ b/contracts/pool/src/tests/stake_deployment.rs @@ -1,5 +1,6 @@ extern crate std; -use soroban_sdk::{testutils::Address as _, Address, Env}; +use phoenix::utils::{LiquidityPoolInitInfo, StakeInitInfo, TokenInitInfo}; +use soroban_sdk::{testutils::Address as _, Address, Env, String}; use super::setup::{deploy_liquidity_pool_contract, deploy_token_contract}; use crate::{ @@ -7,6 +8,9 @@ use crate::{ storage::{Config, PairType}, }; +use crate::contract::{LiquidityPool, LiquidityPoolClient}; +use crate::tests::setup::{install_stake_wasm, install_token_wasm}; + #[test] fn confirm_stake_contract_deployment() { let env = Env::default(); @@ -73,3 +77,75 @@ fn confirm_stake_contract_deployment() { } ); } + +#[test] +#[should_panic(expected = "Pool: Initialize: initializing contract twice is not allowed")] +fn second_pool_deployment_should_fail() { + let env = Env::default(); + env.mock_all_auths(); + env.cost_estimate().budget().reset_unlimited(); + + let mut admin1 = Address::generate(&env); + let mut admin2 = Address::generate(&env); + let user = Address::generate(&env); + + let mut token1 = deploy_token_contract(&env, &admin1); + let mut token2 = deploy_token_contract(&env, &admin2); + if token2.address < token1.address { + std::mem::swap(&mut token1, &mut token2); + std::mem::swap(&mut admin1, &mut admin2); + } + + let pool = LiquidityPoolClient::new(&env, &env.register(LiquidityPool, ())); + + let token_wasm_hash = install_token_wasm(&env); + let stake_wasm_hash = install_stake_wasm(&env); + let fee_recipient = user; + let max_allowed_slippage = 5_000i64; // 50% if not specified + let max_allowed_spread = 500i64; // 5% if not specified + + let token_init_info = TokenInitInfo { + token_a: token1.address.clone(), + token_b: token2.address.clone(), + }; + let stake_init_info = StakeInitInfo { + min_bond: 10i128, + min_reward: 5i128, + manager: Address::generate(&env), + max_complexity: 10u32, + }; + + let lp_init_info = LiquidityPoolInitInfo { + admin: admin1, + swap_fee_bps: 0i64, + fee_recipient, + max_allowed_slippage_bps: max_allowed_slippage, + default_slippage_bps: 2_500, + max_allowed_spread_bps: max_allowed_spread, + max_referral_bps: 500, + token_init_info, + stake_init_info, + }; + + pool.initialize( + &stake_wasm_hash, + &token_wasm_hash, + &lp_init_info, + &Address::generate(&env), + &String::from_str(&env, "Pool"), + &String::from_str(&env, "PHOBTC"), + &100i64, + &1_000, + ); + + pool.initialize( + &stake_wasm_hash, + &token_wasm_hash, + &lp_init_info, + &Address::generate(&env), + &String::from_str(&env, "Pool"), + &String::from_str(&env, "PHOBTC"), + &100i64, + &1_000, + ); +} diff --git a/contracts/pool_stable/src/contract.rs b/contracts/pool_stable/src/contract.rs index 2fee3e219..ce80cee70 100644 --- a/contracts/pool_stable/src/contract.rs +++ b/contracts/pool_stable/src/contract.rs @@ -9,10 +9,11 @@ use soroban_sdk::{ use crate::{ error::ContractError, math::{calc_y, compute_current_amp, compute_d, scale_value, AMP_PRECISION}, + stake_contract, storage::{ get_amp, get_config, get_greatest_precision, get_precisions, save_amp, save_config, save_greatest_precision, - utils::{self, get_admin_old}, + utils::{self, get_admin_old, is_initialized, set_initialized}, AmplifierParameters, Asset, Config, PairType, PoolResponse, SimulateReverseSwapResponse, SimulateSwapResponse, StableLiquidityPoolInfo, ADMIN, }, @@ -36,6 +37,21 @@ pub struct StableLiquidityPool; #[allow(dead_code)] pub trait StableLiquidityPoolTrait { + // Sets the token contract addresses for this pool + // token_wasm_hash is the WASM hash of the deployed token contract for the pool share token + #[allow(clippy::too_many_arguments)] + fn initialize( + env: Env, + stake_wasm_hash: BytesN<32>, + token_wasm_hash: BytesN<32>, + lp_init_info: LiquidityPoolInitInfo, + factory_addr: Address, + share_token_name: String, + share_token_symbol: String, + amp: u64, + max_allowed_fee_bps: i64, + ); + // Deposits token_a and token_b. Also mints pool shares for the "to" Identifier. The amount minted // is determined based on the difference between the reserves stored by this contract, and // the actual balance of token_a and token_b for this contract. @@ -127,6 +143,134 @@ pub trait StableLiquidityPoolTrait { #[contractimpl] impl StableLiquidityPoolTrait for StableLiquidityPool { + #[allow(clippy::too_many_arguments)] + fn initialize( + env: Env, + stake_wasm_hash: BytesN<32>, + token_wasm_hash: BytesN<32>, + lp_init_info: LiquidityPoolInitInfo, + factory_addr: Address, + share_token_name: String, + share_token_symbol: String, + amp: u64, + max_allowed_fee_bps: i64, + ) { + if is_initialized(&env) { + log!( + &env, + "Pool stable: Initialize: initializing contract twice is not allowed" + ); + panic_with_error!(&env, ContractError::AlreadyInitialized); + } + + let admin = lp_init_info.admin; + let swap_fee_bps = lp_init_info.swap_fee_bps; + let fee_recipient = lp_init_info.fee_recipient; + let max_allowed_slippage_bps = lp_init_info.max_allowed_slippage_bps; + let default_slippage_bps = lp_init_info.default_slippage_bps; + let max_allowed_spread_bps = lp_init_info.max_allowed_spread_bps; + let token_init_info = lp_init_info.token_init_info; + let stake_init_info = lp_init_info.stake_init_info; + + validate_bps!( + swap_fee_bps, + max_allowed_slippage_bps, + max_allowed_spread_bps, + default_slippage_bps, + max_allowed_fee_bps + ); + + // if the swap_fee_bps is above the threshold, we throw an error + if swap_fee_bps > max_allowed_fee_bps { + log!( + &env, + "Pool: Initialize: swap fee is higher than the maximum allowed!" + ); + panic_with_error!(&env, ContractError::SwapFeeBpsOverLimit); + } + + set_initialized(&env); + + // Token info + let token_a = token_init_info.token_a; + let token_b = token_init_info.token_b; + // Contract info + let min_bond = stake_init_info.min_bond; + let min_reward = stake_init_info.min_reward; + let manager = stake_init_info.manager; + + // Token order validation to make sure only one instance of a pool can exist + if token_a >= token_b { + log!( + &env, + "Pool Stable: Initialize: First token must be alphabetically smaller than second token" + ); + panic_with_error!(&env, ContractError::TokenABiggerThanTokenB); + } + + let decimals = save_greatest_precision(&env, &token_a, &token_b); + + // deploy and initialize token contract + let share_token_address = utils::deploy_token_contract( + &env, + token_wasm_hash, + &token_a, + &token_b, + env.current_contract_address(), + decimals, + share_token_name, + share_token_symbol, + ); + + let stake_contract_address = utils::deploy_stake_contract(&env, stake_wasm_hash); + stake_contract::Client::new(&env, &stake_contract_address).initialize( + &admin, + &share_token_address, + &min_bond, + &min_reward, + &manager, + &factory_addr, + &stake_init_info.max_complexity, + ); + + let config = Config { + token_a: token_a.clone(), + token_b: token_b.clone(), + share_token: share_token_address, + stake_contract: stake_contract_address, + pool_type: PairType::Stable, + total_fee_bps: swap_fee_bps, + fee_recipient, + max_allowed_slippage_bps, + default_slippage_bps, + max_allowed_spread_bps, + }; + save_config(&env, config); + let current_time = env.ledger().timestamp(); + if amp == 0 || amp > MAX_AMP { + log!(&env, "Pool Stable: Initialize: AMP parameter is incorrect"); + panic_with_error!(&env, ContractError::InvalidAMP); + } + save_amp( + &env, + AmplifierParameters { + init_amp: amp * AMP_PRECISION, + init_amp_time: current_time, + next_amp: amp * AMP_PRECISION, + next_amp_time: current_time, + }, + ); + utils::save_admin_old(&env, admin); + utils::save_total_shares(&env, 0); + utils::save_pool_balance_a(&env, 0); + utils::save_pool_balance_b(&env, 0); + + env.events() + .publish(("initialize", "XYK LP token_a"), token_a); + env.events() + .publish(("initialize", "XYK LP token_b"), token_b); + } + fn provide_liquidity( env: Env, sender: Address, @@ -682,124 +826,6 @@ impl StableLiquidityPoolTrait for StableLiquidityPool { #[contractimpl] impl StableLiquidityPool { - #[allow(clippy::too_many_arguments)] - pub fn __constructor( - env: Env, - stake_wasm_hash: BytesN<32>, - token_wasm_hash: BytesN<32>, - lp_init_info: LiquidityPoolInitInfo, - factory_addr: Address, - share_token_name: String, - share_token_symbol: String, - amp: u64, - max_allowed_fee_bps: i64, - ) { - let admin = lp_init_info.admin; - let swap_fee_bps = lp_init_info.swap_fee_bps; - let fee_recipient = lp_init_info.fee_recipient; - let max_allowed_slippage_bps = lp_init_info.max_allowed_slippage_bps; - let default_slippage_bps = lp_init_info.default_slippage_bps; - let max_allowed_spread_bps = lp_init_info.max_allowed_spread_bps; - let token_init_info = lp_init_info.token_init_info; - let stake_init_info = lp_init_info.stake_init_info; - - validate_bps!( - swap_fee_bps, - max_allowed_slippage_bps, - max_allowed_spread_bps, - default_slippage_bps, - max_allowed_fee_bps - ); - - // if the swap_fee_bps is above the threshold, we throw an error - if swap_fee_bps > max_allowed_fee_bps { - log!( - &env, - "Pool: Initialize: swap fee is higher than the maximum allowed!" - ); - panic_with_error!(&env, ContractError::SwapFeeBpsOverLimit); - } - - // Token info - let token_a = token_init_info.token_a; - let token_b = token_init_info.token_b; - // Contract info - let min_bond = stake_init_info.min_bond; - let min_reward = stake_init_info.min_reward; - let manager = stake_init_info.manager; - - // Token order validation to make sure only one instance of a pool can exist - if token_a >= token_b { - log!( - &env, - "Pool Stable: Initialize: First token must be alphabetically smaller than second token" - ); - panic_with_error!(&env, ContractError::TokenABiggerThanTokenB); - } - - let decimals = save_greatest_precision(&env, &token_a, &token_b); - - // deploy and initialize token contract - let share_token_address = utils::deploy_token_contract( - &env, - token_wasm_hash, - &token_a, - &token_b, - env.current_contract_address(), - decimals, - share_token_name, - share_token_symbol, - ); - - let stake_contract_address = utils::deploy_stake_contract( - &env, - stake_wasm_hash, - &admin, - &share_token_address, - min_bond, - min_reward, - &manager, - &factory_addr, - stake_init_info.max_complexity, - ); - - let config = Config { - token_a: token_a.clone(), - token_b: token_b.clone(), - share_token: share_token_address, - stake_contract: stake_contract_address, - pool_type: PairType::Stable, - total_fee_bps: swap_fee_bps, - fee_recipient, - max_allowed_slippage_bps, - default_slippage_bps, - max_allowed_spread_bps, - }; - save_config(&env, config); - let current_time = env.ledger().timestamp(); - if amp == 0 || amp > MAX_AMP { - log!(&env, "Pool Stable: Initialize: AMP parameter is incorrect"); - panic_with_error!(&env, ContractError::InvalidAMP); - } - save_amp( - &env, - AmplifierParameters { - init_amp: amp * AMP_PRECISION, - init_amp_time: current_time, - next_amp: amp * AMP_PRECISION, - next_amp_time: current_time, - }, - ); - utils::save_admin_old(&env, admin); - utils::save_total_shares(&env, 0); - utils::save_pool_balance_a(&env, 0); - utils::save_pool_balance_b(&env, 0); - - env.events() - .publish(("initialize", "XYK LP token_a"), token_a); - env.events() - .publish(("initialize", "XYK LP token_b"), token_b); - } #[allow(dead_code)] pub fn update(env: Env, new_wasm_hash: BytesN<32>) { let admin = get_admin_old(&env); diff --git a/contracts/pool_stable/src/error.rs b/contracts/pool_stable/src/error.rs index 5c8afb6b5..a8f8bfd4d 100644 --- a/contracts/pool_stable/src/error.rs +++ b/contracts/pool_stable/src/error.rs @@ -10,21 +10,22 @@ pub enum ContractError { ValidateFeeBpsTotalFeesCantBeGreaterThan100 = 4, TotalSharesEqualZero = 5, AssetNotInPool = 6, - TokenABiggerThanTokenB = 7, - InvalidBps = 8, - LowLiquidity = 9, - Unauthorized = 10, - IncorrectAssetSwap = 11, - NewtonMethodFailed = 12, - CalcYErr = 13, - SwapMinReceivedBiggerThanReturn = 14, - ProvideLiquidityBothTokensMustBeMoreThanZero = 15, - DivisionByZero = 16, - InvalidAMP = 17, - TransactionAfterTimestampDeadline = 18, - SlippageToleranceExceeded = 19, - IssuedSharesLessThanUserRequested = 20, - SwapFeeBpsOverLimit = 21, - UserDeclinesPoolFee = 22, - AdminNotSet = 23, + AlreadyInitialized = 7, + TokenABiggerThanTokenB = 8, + InvalidBps = 9, + LowLiquidity = 10, + Unauthorized = 11, + IncorrectAssetSwap = 12, + NewtonMethodFailed = 13, + CalcYErr = 14, + SwapMinReceivedBiggerThanReturn = 15, + ProvideLiquidityBothTokensMustBeMoreThanZero = 16, + DivisionByZero = 17, + InvalidAMP = 18, + TransactionAfterTimestampDeadline = 19, + SlippageToleranceExceeded = 20, + IssuedSharesLessThanUserRequested = 21, + SwapFeeBpsOverLimit = 22, + UserDeclinesPoolFee = 23, + AdminNotSet = 24, } diff --git a/contracts/pool_stable/src/storage.rs b/contracts/pool_stable/src/storage.rs index 8dd54bd69..57b79f1f3 100644 --- a/contracts/pool_stable/src/storage.rs +++ b/contracts/pool_stable/src/storage.rs @@ -16,7 +16,7 @@ pub enum DataKey { ReserveA = 1, ReserveB = 2, Admin = 3, - Initialized = 4, // deprecated, do not remove for now + Initialized = 4, Amp = 5, MaxPrecision = 6, TokenPrecision = 7, @@ -246,33 +246,13 @@ pub mod utils { .deploy_v2(token_wasm_hash, (admin, decimals, name, symbol)) } - #[allow(clippy::too_many_arguments)] - pub fn deploy_stake_contract( - e: &Env, - stake_wasm_hash: BytesN<32>, - admin: &Address, - share_token_address: &Address, - min_bond: i128, - min_reward: i128, - manager: &Address, - factory_addr: &Address, - max_complexity: u32, - ) -> Address { + pub fn deploy_stake_contract(e: &Env, stake_wasm_hash: BytesN<32>) -> Address { let salt = Bytes::new(e); let salt = e.crypto().sha256(&salt); - e.deployer().with_current_contract(salt).deploy_v2( - stake_wasm_hash, - ( - admin, - share_token_address, - min_bond, - min_reward, - manager, - factory_addr, - max_complexity, - ), - ) + e.deployer() + .with_current_contract(salt) + .deploy_v2(stake_wasm_hash, ()) } pub fn save_admin_old(e: &Env, address: Address) { @@ -376,6 +356,22 @@ pub mod utils { pub fn get_balance(e: &Env, contract: &Address) -> i128 { token_contract::Client::new(e, contract).balance(&e.current_contract_address()) } + + pub fn is_initialized(e: &Env) -> bool { + e.storage() + .persistent() + .get(&DataKey::Initialized) + .unwrap_or(false) + } + + pub fn set_initialized(e: &Env) { + e.storage().persistent().set(&DataKey::Initialized, &true); + e.storage().persistent().extend_ttl( + &DataKey::Initialized, + PERSISTENT_LIFETIME_THRESHOLD, + PERSISTENT_BUMP_AMOUNT, + ); + } } #[cfg(test)] diff --git a/contracts/pool_stable/src/tests/setup.rs b/contracts/pool_stable/src/tests/setup.rs index 5f97a775d..3640e5966 100644 --- a/contracts/pool_stable/src/tests/setup.rs +++ b/contracts/pool_stable/src/tests/setup.rs @@ -51,6 +51,7 @@ pub fn deploy_stable_liquidity_pool_contract<'a>( init_amp: impl Into>, ) -> StableLiquidityPoolClient<'a> { let admin = admin.into().unwrap_or(Address::generate(env)); + let pool = StableLiquidityPoolClient::new(env, &env.register(StableLiquidityPool, ())); let fee_recipient = fee_recipient .into() .unwrap_or_else(|| Address::generate(env)); @@ -82,22 +83,15 @@ pub fn deploy_stable_liquidity_pool_contract<'a>( stake_init_info, }; - let pool = StableLiquidityPoolClient::new( - env, - &env.register( - StableLiquidityPool, - ( - &stake_wasm_hash, - &token_wasm_hash, - lp_init_info, - &factory, - String::from_str(env, "LP_SHARE_TOKEN"), - String::from_str(env, "PHOBTCLP"), - &init_amp.into().unwrap_or(6u64), - &1_000i64, - ), - ), + pool.initialize( + &stake_wasm_hash, + &token_wasm_hash, + &lp_init_info, + &factory, + &String::from_str(env, "LP_SHARE_TOKEN"), + &String::from_str(env, "PHOBTCLP"), + &init_amp.into().unwrap_or(6u64), + &1_000, ); - pool } diff --git a/contracts/pool_stable/src/tests/stake_deployment.rs b/contracts/pool_stable/src/tests/stake_deployment.rs index 72705e5d6..d68c9eaf0 100644 --- a/contracts/pool_stable/src/tests/stake_deployment.rs +++ b/contracts/pool_stable/src/tests/stake_deployment.rs @@ -79,6 +79,81 @@ fn confirm_stake_contract_deployment() { ); } +#[test] +#[should_panic(expected = "Pool stable: Initialize: initializing contract twice is not allowed")] +fn second_pool_stable_deployment_should_fail() { + let env = Env::default(); + env.mock_all_auths(); + env.cost_estimate().budget().reset_unlimited(); + + let mut admin1 = Address::generate(&env); + let mut admin2 = Address::generate(&env); + let user = Address::generate(&env); + + let mut token1 = deploy_token_contract(&env, &admin1); + let mut token2 = deploy_token_contract(&env, &admin2); + if token2.address < token1.address { + std::mem::swap(&mut token1, &mut token2); + std::mem::swap(&mut admin1, &mut admin2); + } + + let pool = StableLiquidityPoolClient::new(&env, &env.register(StableLiquidityPool, ())); + + let token_wasm_hash = install_token_wasm(&env); + let stake_wasm_hash = install_stake_wasm(&env); + let _stake_reward_wasm_hash = install_stake_rewards_wasm(&env); + let fee_recipient = user; + let max_allowed_slippage = 5_000i64; // 50% if not specified + let max_allowed_spread = 500i64; // 5% if not specified + let amp = 6u64; + let stake_manager = Address::generate(&env); + let factory = Address::generate(&env); + + let token_init_info = TokenInitInfo { + token_a: token1.address.clone(), + token_b: token2.address.clone(), + }; + let stake_init_info = StakeInitInfo { + min_bond: 10i128, + min_reward: 5i128, + manager: stake_manager.clone(), + max_complexity: 10, + }; + + let lp_init_info = LiquidityPoolInitInfo { + admin: admin1, + swap_fee_bps: 0i64, + fee_recipient, + max_allowed_slippage_bps: max_allowed_slippage, + default_slippage_bps: 2_500, + max_allowed_spread_bps: max_allowed_spread, + max_referral_bps: 500, + token_init_info, + stake_init_info, + }; + + pool.initialize( + &stake_wasm_hash, + &token_wasm_hash, + &lp_init_info, + &factory, + &String::from_str(&env, "LP_SHARE_TOKEN"), + &String::from_str(&env, "PHOBTCLP"), + &, + &150, + ); + pool.initialize( + &stake_wasm_hash, + &token_wasm_hash, + &lp_init_info, + &factory, + &String::from_str(&env, "LP_SHARE_TOKEN"), + &String::from_str(&env, "PHOBTCLP"), + &, + &150, + ); +} + #[test] #[should_panic( expected = "Pool Stable: Initialize: First token must be alphabetically smaller than second token" @@ -99,6 +174,8 @@ fn pool_stable_initialization_should_fail_with_token_a_bigger_than_token_b() { std::mem::swap(&mut admin2, &mut admin1); } + let pool = StableLiquidityPoolClient::new(&env, &env.register(StableLiquidityPool, ())); + let token_wasm_hash = install_token_wasm(&env); let stake_wasm_hash = install_stake_wasm(&env); let _stake_reward_wasm_hash = install_stake_rewards_wasm(&env); @@ -131,20 +208,15 @@ fn pool_stable_initialization_should_fail_with_token_a_bigger_than_token_b() { token_init_info, stake_init_info, }; - let _ = StableLiquidityPoolClient::new( - &env, - &env.register( - StableLiquidityPool, - ( - &stake_wasm_hash, - &token_wasm_hash, - lp_init_info, - &factory, - String::from_str(&env, "LP_SHARE_TOKEN"), - String::from_str(&env, "PHOBTCLP"), - &, - &150i64, - ), - ), + + pool.initialize( + &stake_wasm_hash, + &token_wasm_hash, + &lp_init_info, + &factory, + &String::from_str(&env, "LP_SHARE_TOKEN"), + &String::from_str(&env, "PHOBTCLP"), + &, + &150, ); } diff --git a/contracts/stake/src/contract.rs b/contracts/stake/src/contract.rs index f10b7e398..de24bf62c 100644 --- a/contracts/stake/src/contract.rs +++ b/contracts/stake/src/contract.rs @@ -15,6 +15,7 @@ use crate::{ get_config, get_stakes, save_config, save_stakes, utils::{ self, add_distribution, get_admin_old, get_distributions, get_total_staked_counter, + is_initialized, set_initialized, }, Config, Stake, ADMIN, }, @@ -32,6 +33,19 @@ pub struct Staking; #[allow(dead_code)] pub trait StakingTrait { + // Sets the token contract addresses for this pool + #[allow(clippy::too_many_arguments)] + fn initialize( + env: Env, + admin: Address, + lp_token: Address, + min_bond: i128, + min_reward: i128, + manager: Address, + owner: Address, + max_complexity: u32, + ); + fn bond(env: Env, sender: Address, tokens: i128); fn unbond(env: Env, sender: Address, stake_amount: i128, stake_timestamp: u64); @@ -65,6 +79,65 @@ pub trait StakingTrait { #[contractimpl] impl StakingTrait for Staking { + #[allow(clippy::too_many_arguments)] + fn initialize( + env: Env, + admin: Address, + lp_token: Address, + min_bond: i128, + min_reward: i128, + manager: Address, + owner: Address, + max_complexity: u32, + ) { + if is_initialized(&env) { + log!( + &env, + "Stake: Initialize: initializing contract twice is not allowed" + ); + panic_with_error!(&env, ContractError::AlreadyInitialized); + } + + set_initialized(&env); + + if min_bond <= 0 { + log!( + &env, + "Stake: initialize: Minimum amount of lp share tokens to bond can not be smaller or equal to 0" + ); + panic_with_error!(&env, ContractError::InvalidMinBond); + } + if min_reward <= 0 { + log!(&env, "Stake: initialize: min_reward must be bigger than 0!"); + panic_with_error!(&env, ContractError::InvalidMinReward); + } + + if max_complexity == 0 { + log!( + &env, + "Stake: initialize: max_complexity must be bigger than 0!" + ); + panic_with_error!(&env, ContractError::InvalidMaxComplexity); + } + + env.events() + .publish(("initialize", "LP Share token staking contract"), &lp_token); + + let config = Config { + lp_token, + min_bond, + min_reward, + manager, + owner, + max_complexity, + }; + save_config(&env, config); + + utils::save_admin_old(&env, &admin); + utils::init_total_staked(&env); + save_total_staked_history(&env, map![&env]); + } + fn bond(env: Env, sender: Address, tokens: i128) { sender.require_auth(); env.storage() @@ -320,55 +393,6 @@ impl StakingTrait for Staking { #[contractimpl] impl Staking { - #[allow(clippy::too_many_arguments)] - pub fn __constructor( - env: Env, - admin: Address, - lp_token: Address, - min_bond: i128, - min_reward: i128, - manager: Address, - owner: Address, - max_complexity: u32, - ) { - if min_bond <= 0 { - log!( - &env, - "Stake: initialize: Minimum amount of lp share tokens to bond can not be smaller or equal to 0" - ); - panic_with_error!(&env, ContractError::InvalidMinBond); - } - if min_reward <= 0 { - log!(&env, "Stake: initialize: min_reward must be bigger than 0!"); - panic_with_error!(&env, ContractError::InvalidMinReward); - } - - if max_complexity == 0 { - log!( - &env, - "Stake: initialize: max_complexity must be bigger than 0!" - ); - panic_with_error!(&env, ContractError::InvalidMaxComplexity); - } - - env.events() - .publish(("initialize", "LP Share token staking contract"), &lp_token); - - let config = Config { - lp_token, - min_bond, - min_reward, - manager, - owner, - max_complexity, - }; - save_config(&env, config); - - utils::save_admin_old(&env, &admin); - utils::init_total_staked(&env); - save_total_staked_history(&env, map![&env]); - } - #[allow(dead_code)] pub fn update(env: Env, new_wasm_hash: BytesN<32>) { let admin = get_admin_old(&env); diff --git a/contracts/stake/src/error.rs b/contracts/stake/src/error.rs index a45baec4e..f56702914 100644 --- a/contracts/stake/src/error.rs +++ b/contracts/stake/src/error.rs @@ -4,17 +4,18 @@ use soroban_sdk::contracterror; #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] #[repr(u32)] pub enum ContractError { - InvalidMinBond = 1, - InvalidMinReward = 2, - InvalidBond = 3, - Unauthorized = 4, - MinRewardNotEnough = 5, - RewardsInvalid = 6, - StakeNotFound = 7, - InvalidTime = 8, - DistributionExists = 9, - InvalidRewardAmount = 10, - InvalidMaxComplexity = 11, - DistributionNotFound = 12, - AdminNotSet = 13, + AlreadyInitialized = 1, + InvalidMinBond = 2, + InvalidMinReward = 3, + InvalidBond = 4, + Unauthorized = 5, + MinRewardNotEnough = 6, + RewardsInvalid = 7, + StakeNotFound = 8, + InvalidTime = 9, + DistributionExists = 10, + InvalidRewardAmount = 11, + InvalidMaxComplexity = 12, + DistributionNotFound = 13, + AdminNotSet = 14, } diff --git a/contracts/stake/src/storage.rs b/contracts/stake/src/storage.rs index da5cd301c..e8864a0ec 100644 --- a/contracts/stake/src/storage.rs +++ b/contracts/stake/src/storage.rs @@ -112,7 +112,7 @@ pub mod utils { Admin = 0, TotalStaked = 1, Distributions = 2, - Initialized = 3, // deprecated, do not remove for now + Initialized = 3, StakeRewards = 4, } @@ -124,6 +124,20 @@ pub mod utils { } } + pub fn is_initialized(e: &Env) -> bool { + e.storage() + .instance() + .get(&DataKey::Initialized) + .unwrap_or(false) + } + + pub fn set_initialized(e: &Env) { + e.storage().instance().set(&DataKey::Initialized, &true); + e.storage() + .instance() + .extend_ttl(PERSISTENT_LIFETIME_THRESHOLD, PERSISTENT_BUMP_AMOUNT); + } + pub fn save_admin_old(e: &Env, address: &Address) { e.storage().persistent().set(&DataKey::Admin, address); e.storage().persistent().extend_ttl( diff --git a/contracts/stake/src/tests/bond.rs b/contracts/stake/src/tests/bond.rs index 6b64f7932..74dba88b2 100644 --- a/contracts/stake/src/tests/bond.rs +++ b/contracts/stake/src/tests/bond.rs @@ -56,6 +56,37 @@ fn initialize_staking_contract() { assert_eq!(response, admin); } +#[test] +#[should_panic(expected = "Stake: Initialize: initializing contract twice is not allowed")] +fn test_deploying_stake_twice_should_fail() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let lp_token = deploy_token_contract(&env, &admin); + let manager = Address::generate(&env); + let owner = Address::generate(&env); + + let first = deploy_staking_contract( + &env, + admin.clone(), + &lp_token.address, + &manager, + &owner, + &DEFAULT_COMPLEXITY, + ); + + first.initialize( + &admin, + &lp_token.address, + &100i128, + &50i128, + &manager, + &owner, + &DEFAULT_COMPLEXITY, + ); +} + #[test] #[should_panic = "Stake: Bond: Trying to stake less than minimum required"] fn bond_too_few() { @@ -378,20 +409,16 @@ fn initialize_staking_contract_should_panic_when_min_bond_invalid() { let env = Env::default(); env.mock_all_auths(); - let _ = StakingClient::new( - &env, - &env.register( - Staking, - ( - &Address::generate(&env), - &Address::generate(&env), - &0i128, - &1_000i128, - &Address::generate(&env), - &Address::generate(&env), - &DEFAULT_COMPLEXITY, - ), - ), + let staking = StakingClient::new(&env, &env.register(Staking, ())); + + staking.initialize( + &Address::generate(&env), + &Address::generate(&env), + &0, + &1_000, + &Address::generate(&env), + &Address::generate(&env), + &DEFAULT_COMPLEXITY, ); } @@ -401,20 +428,16 @@ fn initialize_staking_contract_should_panic_when_min_rewards_invalid() { let env = Env::default(); env.mock_all_auths(); - let _ = StakingClient::new( - &env, - &env.register( - Staking, - ( - &Address::generate(&env), - &Address::generate(&env), - &1_000i128, - &0i128, - &Address::generate(&env), - &Address::generate(&env), - &DEFAULT_COMPLEXITY, - ), - ), + let staking = StakingClient::new(&env, &env.register(Staking, ())); + + staking.initialize( + &Address::generate(&env), + &Address::generate(&env), + &1_000, + &0, + &Address::generate(&env), + &Address::generate(&env), + &DEFAULT_COMPLEXITY, ); } @@ -424,19 +447,15 @@ fn initialize_staking_contract_should_panic_when_max_complexity_invalid() { let env = Env::default(); env.mock_all_auths(); - let _ = StakingClient::new( - &env, - &env.register( - Staking, - ( - &Address::generate(&env), - &Address::generate(&env), - &1_000i128, - &1_000i128, - &Address::generate(&env), - &Address::generate(&env), - &0u32, - ), - ), + let staking = StakingClient::new(&env, &env.register(Staking, ())); + + staking.initialize( + &Address::generate(&env), + &Address::generate(&env), + &1_000, + &1_000, + &Address::generate(&env), + &Address::generate(&env), + &0u32, ); } diff --git a/contracts/stake/src/tests/setup.rs b/contracts/stake/src/tests/setup.rs index 348ad4d29..9d393a5ac 100644 --- a/contracts/stake/src/tests/setup.rs +++ b/contracts/stake/src/tests/setup.rs @@ -40,22 +40,17 @@ pub fn deploy_staking_contract<'a>( max_complexity: &u32, ) -> StakingClient<'a> { let admin = admin.into().unwrap_or(Address::generate(env)); - let staking = StakingClient::new( - env, - &env.register( - Staking, - ( - &admin, - lp_token, - &MIN_BOND, - &MIN_REWARD, - manager, - owner, - max_complexity, - ), - ), - ); + let staking = StakingClient::new(env, &env.register(Staking, ())); + staking.initialize( + &admin, + lp_token, + &MIN_BOND, + &MIN_REWARD, + manager, + owner, + max_complexity, + ); staking } diff --git a/contracts/stake_rewards/src/contract.rs b/contracts/stake_rewards/src/contract.rs index 0a2d9f935..a320372cb 100644 --- a/contracts/stake_rewards/src/contract.rs +++ b/contracts/stake_rewards/src/contract.rs @@ -18,7 +18,7 @@ use crate::{ msg::{AnnualizedRewardResponse, ConfigResponse, WithdrawableRewardResponse}, storage::{ get_config, save_config, - utils::{self, get_admin_old}, + utils::{self, get_admin_old, is_initialized, set_initialized}, BondingInfo, Config, }, token_contract, @@ -36,6 +36,18 @@ pub struct StakingRewards; #[allow(dead_code)] pub trait StakingRewardsTrait { + // Sets the token contract addresses for this pool + #[allow(clippy::too_many_arguments)] + fn initialize( + env: Env, + admin: Address, + staking_contract: Address, + reward_token: Address, + max_complexity: u32, + min_reward: i128, + min_bond: i128, + ); + fn add_user(env: Env, user: Address, stakes: BondingInfo); fn calculate_bond(env: Env, sender: Address, stakes: BondingInfo); @@ -71,6 +83,59 @@ pub trait StakingRewardsTrait { #[contractimpl] impl StakingRewardsTrait for StakingRewards { + #[allow(clippy::too_many_arguments)] + fn initialize( + env: Env, + admin: Address, + staking_contract: Address, + reward_token: Address, + max_complexity: u32, + min_reward: i128, + min_bond: i128, + ) { + if is_initialized(&env) { + log!( + &env, + "Stake rewards: Initialize: initializing contract twice is not allowed" + ); + panic_with_error!(&env, ContractError::AlreadyInitialized); + } + + set_initialized(&env); + + env.events().publish( + ("initialize", "StakingRewards rewards distribution contract"), + (), + ); + + let config = Config { + staking_contract, + reward_token: reward_token.clone(), + max_complexity, + min_reward, + min_bond, + }; + save_config(&env, config); + + let distribution = Distribution { + shares_per_point: 1u128, + shares_leftover: 0u64, + distributed_total: 0u128, + withdrawable_total: 0u128, + max_bonus_bps: 0u64, + bonus_per_day_bps: 0u64, + }; + + save_distribution(&env, &reward_token, &distribution); + // Create the default reward distribution curve which is just a flat 0 const + save_reward_curve(&env, reward_token.clone(), &Curve::Constant(0)); + + env.events() + .publish(("create_distribution_flow", "asset"), &reward_token); + + utils::save_admin_old(&env, &admin); + } + fn add_user(env: Env, user: Address, stakes: BondingInfo) { let config = get_config(&env); // only Staking contract which deployed this one can call this method @@ -481,49 +546,6 @@ impl StakingRewardsTrait for StakingRewards { #[contractimpl] impl StakingRewards { - #[allow(clippy::too_many_arguments)] - pub fn __constructor( - env: Env, - admin: Address, - staking_contract: Address, - reward_token: Address, - max_complexity: u32, - min_reward: i128, - min_bond: i128, - ) { - env.events().publish( - ("initialize", "StakingRewards rewards distribution contract"), - (), - ); - - let config = Config { - staking_contract, - reward_token: reward_token.clone(), - max_complexity, - min_reward, - min_bond, - }; - save_config(&env, config); - - let distribution = Distribution { - shares_per_point: 1u128, - shares_leftover: 0u64, - distributed_total: 0u128, - withdrawable_total: 0u128, - max_bonus_bps: 0u64, - bonus_per_day_bps: 0u64, - }; - - save_distribution(&env, &reward_token, &distribution); - // Create the default reward distribution curve which is just a flat 0 const - save_reward_curve(&env, reward_token.clone(), &Curve::Constant(0)); - - env.events() - .publish(("create_distribution_flow", "asset"), &reward_token); - - utils::save_admin_old(&env, &admin); - } - #[allow(dead_code)] pub fn update(env: Env, new_wasm_hash: BytesN<32>) { let admin = get_admin_old(&env); diff --git a/contracts/stake_rewards/src/error.rs b/contracts/stake_rewards/src/error.rs index fcab1c6d4..3587d74c8 100644 --- a/contracts/stake_rewards/src/error.rs +++ b/contracts/stake_rewards/src/error.rs @@ -4,16 +4,17 @@ use soroban_sdk::contracterror; #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] #[repr(u32)] pub enum ContractError { - InvalidMinBond = 1, - InvalidMinReward = 2, - InvalidBond = 3, - Unauthorized = 4, - MinRewardNotEnough = 5, - RewardsInvalid = 6, - StakeNotFound = 7, - InvalidTime = 8, - DistributionExists = 9, - InvalidRewardAmount = 10, - InvalidMaxComplexity = 11, - AdminNotSet = 12, + AlreadyInitialized = 1, + InvalidMinBond = 2, + InvalidMinReward = 3, + InvalidBond = 4, + Unauthorized = 5, + MinRewardNotEnough = 6, + RewardsInvalid = 7, + StakeNotFound = 8, + InvalidTime = 9, + DistributionExists = 10, + InvalidRewardAmount = 11, + InvalidMaxComplexity = 12, + AdminNotSet = 13, } diff --git a/contracts/stake_rewards/src/storage.rs b/contracts/stake_rewards/src/storage.rs index a59dd93c9..527bd10f2 100644 --- a/contracts/stake_rewards/src/storage.rs +++ b/contracts/stake_rewards/src/storage.rs @@ -55,7 +55,7 @@ pub mod utils { #[derive(Clone, Copy)] #[repr(u32)] pub enum DataKey { - Initialized = 0, // deprecated, do not remove for now + Initialized = 0, Admin = 1, } @@ -67,6 +67,22 @@ pub mod utils { } } + pub fn is_initialized(e: &Env) -> bool { + e.storage() + .persistent() + .get(&DataKey::Initialized) + .unwrap_or(false) + } + + pub fn set_initialized(e: &Env) { + e.storage().persistent().set(&DataKey::Initialized, &true); + e.storage().persistent().extend_ttl( + &DataKey::Initialized, + PERSISTENT_LIFETIME_THRESHOLD, + PERSISTENT_BUMP_AMOUNT, + ); + } + pub fn save_admin_old(e: &Env, address: &Address) { e.storage().persistent().set(&DataKey::Admin, address); e.storage().persistent().extend_ttl( diff --git a/contracts/stake_rewards/src/tests/setup.rs b/contracts/stake_rewards/src/tests/setup.rs index 1785cf203..341a5409e 100644 --- a/contracts/stake_rewards/src/tests/setup.rs +++ b/contracts/stake_rewards/src/tests/setup.rs @@ -23,20 +23,15 @@ pub fn deploy_staking_rewards_contract<'a>( reward_token: &Address, staking_contract: &Address, ) -> StakingRewardsClient<'a> { - let staking_rewards = StakingRewardsClient::new( - env, - &env.register( - StakingRewards, - ( - admin, - staking_contract, - reward_token, - &MAX_COMPLEXITY, - &MIN_REWARD, - &MIN_BOND, - ), - ), - ); + let staking_rewards = StakingRewardsClient::new(env, &env.register(StakingRewards, ())); + staking_rewards.initialize( + admin, + staking_contract, + reward_token, + &MAX_COMPLEXITY, + &MIN_REWARD, + &MIN_BOND, + ); staking_rewards } diff --git a/contracts/trader/src/contract.rs b/contracts/trader/src/contract.rs index c1226d97b..4ad5d7fad 100644 --- a/contracts/trader/src/contract.rs +++ b/contracts/trader/src/contract.rs @@ -7,8 +7,9 @@ use crate::{ error::ContractError, lp_contract, storage::{ - get_admin_old, get_name, get_output_token, get_pair, save_admin_old, save_name, - save_output_token, save_pair, Asset, BalanceInfo, OutputTokenInfo, ADMIN, + get_admin_old, get_name, get_output_token, get_pair, is_initialized, save_admin_old, + save_name, save_output_token, save_pair, set_initialized, Asset, BalanceInfo, + OutputTokenInfo, ADMIN, }, token_contract, }; @@ -23,6 +24,14 @@ pub struct Trader; #[allow(dead_code)] pub trait TraderTrait { + fn initialize( + env: Env, + admin: Address, + contract_name: String, + pair_addresses: (Address, Address), + output_token: Address, + ); + #[allow(clippy::too_many_arguments)] fn trade_token( env: Env, @@ -59,6 +68,40 @@ pub trait TraderTrait { #[contractimpl] impl TraderTrait for Trader { + fn initialize( + env: Env, + admin: Address, + contract_name: String, + pair_addresses: (Address, Address), + output_token: Address, + ) { + admin.require_auth(); + + if is_initialized(&env) { + log!(&env, "Trader: Initialize: Cannot initialize trader twice!"); + panic_with_error!(env, ContractError::AlreadyInitialized) + } + + save_admin_old(&env, &admin); + + save_name(&env, &contract_name); + + save_pair(&env, &pair_addresses); + + save_output_token(&env, &output_token); + + set_initialized(&env); + + env.events() + .publish(("Trader: Initialize", "admin: "), &admin); + env.events() + .publish(("Trader: Initialize", "contract name: "), contract_name); + env.events() + .publish(("Trader: Initialize", "pairs: "), pair_addresses); + env.events() + .publish(("Trader: Initialize", "PHO token: "), output_token); + } + #[allow(clippy::too_many_arguments)] fn trade_token( env: Env, @@ -235,33 +278,3 @@ impl TraderTrait for Trader { Ok(()) } } - -#[contractimpl] -impl Trader { - pub fn __constructor( - env: Env, - admin: Address, - contract_name: String, - pair_addresses: (Address, Address), - output_token: Address, - ) { - admin.require_auth(); - - save_admin_old(&env, &admin); - - save_name(&env, &contract_name); - - save_pair(&env, &pair_addresses); - - save_output_token(&env, &output_token); - - env.events() - .publish(("Trader: Initialize", "admin: "), &admin); - env.events() - .publish(("Trader: Initialize", "contract name: "), contract_name); - env.events() - .publish(("Trader: Initialize", "pairs: "), pair_addresses); - env.events() - .publish(("Trader: Initialize", "PHO token: "), output_token); - } -} diff --git a/contracts/trader/src/error.rs b/contracts/trader/src/error.rs index 08efe7304..ec32bb68f 100644 --- a/contracts/trader/src/error.rs +++ b/contracts/trader/src/error.rs @@ -13,5 +13,6 @@ pub enum ContractError { SwapTokenNotInPair = 7, InvalidMaxSpreadBps = 8, InitValueNotFound = 9, - AdminNotSet = 10, + AlreadyInitialized = 10, + AdminNotSet = 11, } diff --git a/contracts/trader/src/storage.rs b/contracts/trader/src/storage.rs index 107c7b46f..d4853e7c6 100644 --- a/contracts/trader/src/storage.rs +++ b/contracts/trader/src/storage.rs @@ -19,7 +19,7 @@ pub enum DataKey { Pair, Token, MaxSpread, - IsInitialized, // deprecated, do not remove for now + IsInitialized, } #[contracttype] @@ -183,3 +183,21 @@ pub fn get_output_token(env: &Env) -> Address { token_addr } + +pub fn set_initialized(env: &Env) { + env.storage() + .persistent() + .set(&DataKey::IsInitialized, &true); + env.storage().persistent().extend_ttl( + &DataKey::IsInitialized, + PERSISTENT_LIFETIME_THRESHOLD, + PERSISTENT_BUMP_AMOUNT, + ); +} + +pub fn is_initialized(env: &Env) -> bool { + env.storage() + .persistent() + .get(&DataKey::IsInitialized) + .unwrap_or_default() +} diff --git a/contracts/trader/src/tests/msgs.rs b/contracts/trader/src/tests/msgs.rs index 3d1bd307a..572c9df0f 100644 --- a/contracts/trader/src/tests/msgs.rs +++ b/contracts/trader/src/tests/msgs.rs @@ -41,10 +41,10 @@ fn initialize() { &String::from_str(&env, "PHO"), ); - let trader_client = deploy_trader_client( - &env, + let trader_client = deploy_trader_client(&env); + trader_client.initialize( &admin, - contract_name.clone(), + &contract_name, &(xlm_token.address.clone(), usdc_token.address.clone()), &pho_token.address, ); @@ -57,6 +57,61 @@ fn initialize() { ); } +#[test] +#[should_panic(expected = "Trader: Initialize: Cannot initialize trader twice!")] +fn initialize_twice_should_panic() { + let env = Env::default(); + env.mock_all_auths(); + env.cost_estimate().budget().reset_unlimited(); + + let admin = Address::generate(&env); + let contract_name = String::from_str(&env, "XLM/USDC"); + let xlm_token = deploy_token_contract( + &env, + &admin, + &6, + &String::from_str(&env, "Stellar"), + &String::from_str(&env, "XLM"), + ); + let usdc_token = deploy_token_contract( + &env, + &admin, + &6, + &String::from_str(&env, "USD Coin"), + &String::from_str(&env, "USDC"), + ); + let pho_token = deploy_token_contract( + &env, + &admin, + &6, + &String::from_str(&env, "Phoenix"), + &String::from_str(&env, "PHO"), + ); + + let trader_client = deploy_trader_client(&env); + trader_client.initialize( + &admin, + &contract_name, + &(xlm_token.address.clone(), usdc_token.address.clone()), + &pho_token.address, + ); + + assert_eq!(trader_client.query_admin_address(), admin); + assert_eq!(trader_client.query_contract_name(), contract_name); + assert_eq!( + trader_client.query_trading_pairs(), + (xlm_token.address.clone(), usdc_token.address.clone()) + ); + + // second time should fail + trader_client.initialize( + &admin, + &contract_name, + &(xlm_token.address.clone(), usdc_token.address.clone()), + &pho_token.address, + ); +} + #[test] fn simple_trade_token_and_transfer_token() { let env = Env::default(); @@ -97,13 +152,7 @@ fn simple_trade_token_and_transfer_token() { xlm_token.mint(&admin, &1_000_000); output_token.mint(&admin, &2_000_000); - let trader_client = deploy_trader_client( - &env, - &admin, - contract_name, - &(xlm_token.address.clone(), usdc_token.address.clone()), - &output_token.address, - ); + let trader_client = deploy_trader_client(&env); let xlm_pho_client: crate::lp_contract::Client<'_> = deploy_and_init_lp_client( &env, @@ -115,6 +164,13 @@ fn simple_trade_token_and_transfer_token() { 0, ); + trader_client.initialize( + &admin, + &contract_name, + &(xlm_token.address.clone(), usdc_token.address.clone()), + &output_token.address, + ); + xlm_token.mint(&trader_client.address, &1_000); assert_eq!( @@ -214,13 +270,7 @@ fn extended_trade_and_transfer_token() { usdc_token.mint(&admin, &3_000_000); output_token.mint(&admin, &2_000_000); - let trader_client = deploy_trader_client( - &env, - &admin, - contract_name, - &(xlm_token.address.clone(), usdc_token.address.clone()), - &output_token.address, - ); + let trader_client = deploy_trader_client(&env); // 1:1 xlm/pho pool let xlm_pho_client: crate::lp_contract::Client<'_> = deploy_and_init_lp_client( @@ -244,6 +294,13 @@ fn extended_trade_and_transfer_token() { 1_000, // 10% swap fee ); + trader_client.initialize( + &admin, + &contract_name, + &(xlm_token.address.clone(), usdc_token.address.clone()), + &output_token.address, + ); + // collected fees from previous txs so we have something to trade against PHO token xlm_token.mint(&trader_client.address, &2_000); usdc_token.mint(&trader_client.address, &3_000); @@ -371,7 +428,7 @@ fn extended_trade_and_transfer_token() { &admin.clone(), &usdc_token.address.clone(), &usdc_pho_client.address, - &Some(1_499), + &Some(1_500), &None::, &None, &None, @@ -392,7 +449,7 @@ fn extended_trade_and_transfer_token() { }, token_b: Asset { symbol: usdc_token.symbol(), - amount: 1 + amount: 0 } } ); @@ -437,13 +494,7 @@ fn trade_token_should_fail_when_unauthorized() { xlm_token.mint(&admin, &1_000_000); pho_token.mint(&admin, &2_000_000); - let trader_client = deploy_trader_client( - &env, - &admin, - contract_name, - &(xlm_token.address.clone(), Address::generate(&env)), - &pho_token.address, - ); + let trader_client = deploy_trader_client(&env); xlm_token.mint(&trader_client.address, &1_000); @@ -457,6 +508,13 @@ fn trade_token_should_fail_when_unauthorized() { 0, ); + trader_client.initialize( + &admin, + &contract_name, + &(xlm_token.address.clone(), Address::generate(&env)), + &pho_token.address, + ); + trader_client.trade_token( &Address::generate(&env), &xlm_token.address.clone(), @@ -503,13 +561,7 @@ fn trade_token_should_fail_when_offered_token_not_in_pair() { xlm_token.mint(&admin, &1_000_000); pho_token.mint(&admin, &2_000_000); - let trader_client = deploy_trader_client( - &env, - &admin, - contract_name, - &(xlm_token.address.clone(), Address::generate(&env)), - &pho_token.address, - ); + let trader_client = deploy_trader_client(&env); xlm_token.mint(&trader_client.address, &1_000); @@ -523,6 +575,13 @@ fn trade_token_should_fail_when_offered_token_not_in_pair() { 0, ); + trader_client.initialize( + &admin, + &contract_name, + &(xlm_token.address.clone(), Address::generate(&env)), + &pho_token.address, + ); + trader_client.trade_token( &admin.clone(), &Address::generate(&env), @@ -570,13 +629,7 @@ fn transfer_should_fail_when_unauthorized() { xlm_token.mint(&admin, &1_000_000); pho_token.mint(&admin, &2_000_000); - let trader_client = deploy_trader_client( - &env, - &admin, - contract_name, - &(xlm_token.address.clone(), Address::generate(&env)), - &pho_token.address, - ); + let trader_client = deploy_trader_client(&env); xlm_token.mint(&trader_client.address, &1_000); @@ -590,6 +643,13 @@ fn transfer_should_fail_when_unauthorized() { 0, ); + trader_client.initialize( + &admin, + &contract_name, + &(xlm_token.address.clone(), Address::generate(&env)), + &pho_token.address, + ); + trader_client.trade_token( &admin.clone(), &xlm_token.address.clone(), @@ -639,13 +699,7 @@ fn transfer_should_fail_with_invalid_spread_bps(max_spread_bps: i64) { xlm_token.mint(&admin, &1_000_000); pho_token.mint(&admin, &2_000_000); - let trader_client = deploy_trader_client( - &env, - &admin, - contract_name, - &(xlm_token.address.clone(), Address::generate(&env)), - &pho_token.address, - ); + let trader_client = deploy_trader_client(&env); xlm_token.mint(&trader_client.address, &1_000); @@ -659,6 +713,13 @@ fn transfer_should_fail_with_invalid_spread_bps(max_spread_bps: i64) { 0, ); + trader_client.initialize( + &admin, + &contract_name, + &(xlm_token.address.clone(), Address::generate(&env)), + &pho_token.address, + ); + trader_client.trade_token( &admin.clone(), &xlm_token.address.clone(), @@ -711,13 +772,7 @@ fn simple_trade_token_and_transfer_token_with_some_ask_asset_min_amount() { xlm_token.mint(&admin, &1_000_000); output_token.mint(&admin, &2_000_000); - let trader_client = deploy_trader_client( - &env, - &admin, - contract_name, - &(xlm_token.address.clone(), usdc_token.address.clone()), - &output_token.address, - ); + let trader_client = deploy_trader_client(&env); let xlm_pho_client: crate::lp_contract::Client<'_> = deploy_and_init_lp_client( &env, @@ -729,6 +784,13 @@ fn simple_trade_token_and_transfer_token_with_some_ask_asset_min_amount() { 0, ); + trader_client.initialize( + &admin, + &contract_name, + &(xlm_token.address.clone(), usdc_token.address.clone()), + &output_token.address, + ); + xlm_token.mint(&trader_client.address, &1_000); // pretty much the same test as `simple_trade_token_and_transfer` but with `Some` value for diff --git a/contracts/trader/src/tests/setup.rs b/contracts/trader/src/tests/setup.rs index 0e2104f44..ce7e2e71e 100644 --- a/contracts/trader/src/tests/setup.rs +++ b/contracts/trader/src/tests/setup.rs @@ -1,6 +1,7 @@ use soroban_sdk::{ testutils::{arbitrary::std, Address as _}, - Address, BytesN, Env, String, + xdr::ToXdr, + Address, Bytes, BytesN, Env, String, }; use crate::{ @@ -24,6 +25,18 @@ pub fn install_stake_wasm(env: &Env) -> BytesN<32> { env.deployer().upload_contract_wasm(WASM) } +pub fn deploy_lp_wasm(env: &Env, admin: Address, token_a: Address, token_b: Address) -> Address { + let factory_wasm = env.deployer().upload_contract_wasm(lp_contract::WASM); + let mut salt = Bytes::new(env); + salt.append(&token_a.to_xdr(env)); + salt.append(&token_b.to_xdr(env)); + let salt = env.crypto().sha256(&salt); + + env.deployer() + .with_address(admin, salt) + .deploy_v2(factory_wasm, ()) +} + pub fn deploy_token_contract<'a>( env: &Env, admin: &Address, @@ -46,6 +59,10 @@ pub fn deploy_and_init_lp_client( token_b_amount: i128, swap_fee_bps: i64, ) -> lp_contract::Client { + let lp_addr = deploy_lp_wasm(env, admin.clone(), token_a.clone(), token_b.clone()); + + let lp_client = lp_contract::Client::new(env, &lp_addr); + let stake_wasm_hash = install_stake_wasm(env); let token_wasm_hash = install_token_wasm(env); @@ -72,21 +89,15 @@ pub fn deploy_and_init_lp_client( stake_init_info, }; - let lp_client = lp_contract::Client::new( - env, - &env.register( - lp_contract::WASM, - ( - &stake_wasm_hash, - &token_wasm_hash, - lp_init_info, - &Address::generate(env), - String::from_str(env, "staked Phoenix"), - String::from_str(env, "sPHO"), - &100i64, - &1_000i64, - ), - ), + lp_client.initialize( + &stake_wasm_hash, + &token_wasm_hash, + &lp_init_info, + &Address::generate(env), + &String::from_str(env, "staked Phoenix"), + &String::from_str(env, "sPHO"), + &100i64, + &1_000, ); lp_client.provide_liquidity( @@ -101,17 +112,6 @@ pub fn deploy_and_init_lp_client( lp_client } -pub fn deploy_trader_client<'a>( - env: &Env, - admin: &Address, - contract_name: String, - token_tuple: &(Address, Address), - pho_token: &Address, -) -> TraderClient<'a> { - let trader_client = TraderClient::new( - env, - &env.register(Trader, (admin, contract_name, token_tuple, pho_token)), - ); - - trader_client +pub fn deploy_trader_client(env: &Env) -> TraderClient { + TraderClient::new(env, &env.register(Trader, ())) } diff --git a/contracts/vesting/src/contract.rs b/contracts/vesting/src/contract.rs index dcd535a85..6e8082e13 100644 --- a/contracts/vesting/src/contract.rs +++ b/contracts/vesting/src/contract.rs @@ -11,10 +11,9 @@ use crate::storage::{get_minter, save_minter, MinterInfo}; use crate::{ error::ContractError, storage::{ - get_admin_old, get_all_vestings, get_config, get_max_vesting_complexity, get_token_info, - get_vesting, save_admin_old, save_config, save_max_vesting_complexity, save_token_info, - save_vesting, update_vesting, Config, VestingInfo, VestingSchedule, VestingTokenInfo, - ADMIN, + get_admin_old, get_all_vestings, get_max_vesting_complexity, get_token_info, get_vesting, + is_initialized, save_admin_old, save_max_vesting_complexity, save_token_info, save_vesting, + set_initialized, update_vesting, VestingInfo, VestingSchedule, VestingTokenInfo, ADMIN, }, token_contract, utils::{check_duplications, validate_vesting_schedule}, @@ -30,6 +29,13 @@ pub struct Vesting; #[allow(dead_code)] pub trait VestingTrait { + fn initialize( + env: Env, + admin: Address, + vesting_token: VestingTokenInfo, + max_vesting_complexity: u32, + ); + fn create_vesting_schedules(env: Env, vesting_accounts: Vec); fn claim(env: Env, sender: Address, index: u64); @@ -48,7 +54,14 @@ pub trait VestingTrait { fn query_available_to_claim(env: Env, address: Address, index: u64) -> i128; - fn query_config(env: Env) -> Config; + #[cfg(feature = "minter")] + fn initialize_with_minter( + env: Env, + admin: Address, + vesting_token: VestingTokenInfo, + max_vesting_complexity: u32, + minter_info: MinterInfo, + ); #[cfg(feature = "minter")] fn burn(env: Env, sender: Address, amount: u128); @@ -70,6 +83,73 @@ pub trait VestingTrait { #[contractimpl] impl VestingTrait for Vesting { + fn initialize( + env: Env, + admin: Address, + vesting_token: VestingTokenInfo, + max_vesting_complexity: u32, + ) { + if is_initialized(&env) { + log!( + &env, + "Stake: Initialize: initializing contract twice is not allowed" + ); + panic_with_error!(&env, ContractError::AlreadyInitialized); + } + + set_initialized(&env); + + save_admin_old(&env, &admin); + + let token_info = VestingTokenInfo { + name: vesting_token.name, + symbol: vesting_token.symbol, + decimals: vesting_token.decimals, + address: vesting_token.address, + }; + + save_token_info(&env, &token_info); + save_max_vesting_complexity(&env, &max_vesting_complexity); + + env.events() + .publish(("Initialize", "Vesting contract with admin: "), admin); + } + + #[cfg(feature = "minter")] + fn initialize_with_minter( + env: Env, + admin: Address, + vesting_token: VestingTokenInfo, + max_vesting_complexity: u32, + minter_info: MinterInfo, + ) { + if is_initialized(&env) { + log!( + &env, + "Stake: Initialize: initializing contract twice is not allowed" + ); + panic_with_error!(&env, ContractError::AlreadyInitialized); + } + + set_initialized(&env); + save_admin_old(&env, &admin); + + save_minter(&env, &minter_info); + + let token_info = VestingTokenInfo { + name: vesting_token.name, + symbol: vesting_token.symbol, + decimals: vesting_token.decimals, + address: vesting_token.address, + }; + + save_token_info(&env, &token_info); + save_max_vesting_complexity(&env, &max_vesting_complexity); + + env.events() + .publish(("Initialize", "Vesting contract with admin: "), admin); + } + fn create_vesting_schedules(env: Env, vesting_schedules: Vec) { let admin = get_admin_old(&env); admin.require_auth(); @@ -186,14 +266,6 @@ impl VestingTrait for Vesting { .publish(("Claim", "Claimed tokens: "), available_to_claim); } - fn query_config(env: Env) -> Config { - env.storage() - .instance() - .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT); - - get_config(&env) - } - #[cfg(feature = "minter")] fn burn(env: Env, sender: Address, amount: u128) { sender.require_auth(); @@ -413,40 +485,3 @@ impl VestingTrait for Vesting { Ok(()) } } - -#[contractimpl] -impl Vesting { - pub fn __constructor( - env: Env, - admin: Address, - vesting_token: VestingTokenInfo, - max_vesting_complexity: u32, - minter_info: Option, - ) { - save_admin_old(&env, &admin); - - let token_info = VestingTokenInfo { - name: vesting_token.name, - symbol: vesting_token.symbol, - decimals: vesting_token.decimals, - address: vesting_token.address, - }; - - let mut config = Config { - is_with_minter: false, - }; - - save_token_info(&env, &token_info); - save_max_vesting_complexity(&env, &max_vesting_complexity); - - if let Some(minter_info) = minter_info { - save_minter(&env, &minter_info); - config.is_with_minter = true; - } - - save_config(&env, config); - - env.events() - .publish(("Initialize", "Vesting contract with admin: "), admin); - } -} diff --git a/contracts/vesting/src/storage.rs b/contracts/vesting/src/storage.rs index dac712195..130c7e819 100644 --- a/contracts/vesting/src/storage.rs +++ b/contracts/vesting/src/storage.rs @@ -41,12 +41,6 @@ pub struct VestingTokenInfo { pub address: Address, } -#[contracttype] -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct Config { - pub is_with_minter: bool, -} - // This structure is used as an argument during the vesting account creation #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] @@ -283,29 +277,16 @@ pub fn get_max_vesting_complexity(env: &Env) -> u32 { vesting_complexity } -pub fn save_config(env: &Env, config: Config) { - env.storage().persistent().set(&DataKey::Config, &config); - - env.storage().persistent().extend_ttl( - &DataKey::Config, - PERSISTENT_LIFETIME_THRESHOLD, - PERSISTENT_BUMP_AMOUNT, - ) +pub fn is_initialized(e: &Env) -> bool { + e.storage() + .instance() + .get(&DataKey::IsInitialized) + .unwrap_or(false) } -pub fn get_config(env: &Env) -> Config { - let config = env - .storage() - .persistent() - .get(&DataKey::Config) - .unwrap_or_else(|| { - log!(&env, "Config not found"); - panic_with_error!(&env, ContractError::NoConfigFound) - }); - env.storage().persistent().extend_ttl( - &DataKey::Config, - PERSISTENT_LIFETIME_THRESHOLD, - PERSISTENT_BUMP_AMOUNT, - ); - config +pub fn set_initialized(e: &Env) { + e.storage().instance().set(&DataKey::IsInitialized, &true); + e.storage() + .instance() + .extend_ttl(PERSISTENT_LIFETIME_THRESHOLD, PERSISTENT_BUMP_AMOUNT); } diff --git a/contracts/vesting/src/tests/claim.rs b/contracts/vesting/src/tests/claim.rs index 7ea757d96..df823796b 100644 --- a/contracts/vesting/src/tests/claim.rs +++ b/contracts/vesting/src/tests/claim.rs @@ -1,5 +1,5 @@ use crate::{ - storage::{MinterInfo, VestingInfo, VestingSchedule, VestingTokenInfo}, + storage::{VestingInfo, VestingSchedule, VestingTokenInfo}, tests::setup::instantiate_vesting_client, }; use curve::{Curve, PiecewiseLinear, SaturatingLinear, Step}; @@ -52,12 +52,12 @@ fn claim_tokens_when_fully_vested() { }, ]; - let vesting_client = - instantiate_vesting_client(&env, &admin, vesting_token, 10u32, None::); + let vesting_client = instantiate_vesting_client(&env); // admin has 320 vesting tokens prior to initializing the contract assert_eq!(token_client.balance(&admin), 320); + vesting_client.initialize(&admin, &vesting_token, &10u32); vesting_client.create_vesting_schedules(&vesting_schedules); // after initialization the admin has 0 vesting tokens @@ -113,12 +113,12 @@ fn transfer_tokens_when_half_vested() { }, ]; - let vesting_client = - instantiate_vesting_client(&env, &admin, vesting_token, 10u32, None::); + let vesting_client = instantiate_vesting_client(&env); // admin has 120 vesting tokens prior to initializing the contract assert_eq!(token_client.balance(&admin), 120); + vesting_client.initialize(&admin, &vesting_token, &10u32); vesting_client.create_vesting_schedules(&vesting_schedules); // after initialization the admin has 0 vesting tokens @@ -174,9 +174,9 @@ fn claim_tokens_once_then_claim_again() { }, ]; - let vesting_client = - instantiate_vesting_client(&env, &admin, vesting_token, 10u32, None::); + let vesting_client = instantiate_vesting_client(&env); + vesting_client.initialize(&admin, &vesting_token, &10u32); vesting_client.create_vesting_schedules(&vesting_schedules); // after initialization the admin has 0 vesting tokens @@ -244,12 +244,12 @@ fn user_can_claim_tokens_way_after_the_testing_period() { }, ]; - let vesting_client = - instantiate_vesting_client(&env, &admin, vesting_token, 10u32, None::); + let vesting_client = instantiate_vesting_client(&env); // admin has 120 vesting tokens prior to initializing the contract assert_eq!(token_client.balance(&admin), 120); + vesting_client.initialize(&admin, &vesting_token, &10u32); vesting_client.create_vesting_schedules(&vesting_schedules); // after initialization the admin has 0 vesting tokens @@ -307,9 +307,9 @@ fn transfer_vesting_token_before_vesting_period_starts_should_fail() { }, ]; - let vesting_client = - instantiate_vesting_client(&env, &admin, vesting_token, 10u32, None::); + let vesting_client = instantiate_vesting_client(&env); + vesting_client.initialize(&admin, &vesting_token, &10u32); vesting_client.create_vesting_schedules(&vesting_schedules); // we set the timestamp at a time earlier than the vesting period start @@ -353,9 +353,9 @@ fn claim_after_all_tokens_have_been_claimed() { }, ]; - let vesting_client = - instantiate_vesting_client(&env, &admin, vesting_token, 10u32, None::); + let vesting_client = instantiate_vesting_client(&env); + vesting_client.initialize(&admin, &vesting_token, &10u32); vesting_client.create_vesting_schedules(&vesting_schedules); env.ledger().with_mut(|li| li.timestamp = 61); @@ -428,9 +428,9 @@ fn transfer_works_with_multiple_users_and_distributions() { }, ]; - let vesting_client = - instantiate_vesting_client(&env, &admin, vesting_token, 10u32, None::); + let vesting_client = instantiate_vesting_client(&env); + vesting_client.initialize(&admin, &vesting_token, &10u32); vesting_client.create_vesting_schedules(&vesting_schedules); // vesting period for our 4 vesters is between 0 and 1_500 @@ -530,12 +530,12 @@ fn claim_works() { }, ]; - let vesting_client = - instantiate_vesting_client(&env, &admin, vesting_token, 10u32, None::); + let vesting_client = instantiate_vesting_client(&env); // admin has 120 vesting tokens prior to initializing the contract assert_eq!(token_client.balance(&admin), 120); + vesting_client.initialize(&admin, &vesting_token, &10u32); vesting_client.create_vesting_schedules(&vesting_schedules); // after initialization the admin has 0 vesting tokens @@ -596,8 +596,8 @@ fn claim_tokens_from_two_distributions() { address: token_client.address.clone(), }; - let vesting_client = - instantiate_vesting_client(&env, &admin, vesting_token, 10u32, None::); + let vesting_client = instantiate_vesting_client(&env); + vesting_client.initialize(&admin, &vesting_token, &10u32); let vesting_schedules = vec![ &env, @@ -738,8 +738,9 @@ fn first_mainnet_simulation() { }, ]; - let vesting_client = - instantiate_vesting_client(&env, &admin, vesting_token, 10u32, None::); + let vesting_client = instantiate_vesting_client(&env); + + vesting_client.initialize(&admin, &vesting_token, &10u32); // we move time to the beginning of the vesting schedule (100s before) env.ledger().with_mut(|li| li.timestamp = 1716817100); diff --git a/contracts/vesting/src/tests/instantiate.rs b/contracts/vesting/src/tests/instantiate.rs index 4bcffa107..b93fb6ab2 100644 --- a/contracts/vesting/src/tests/instantiate.rs +++ b/contracts/vesting/src/tests/instantiate.rs @@ -1,7 +1,7 @@ use soroban_sdk::{testutils::Address as _, vec, Address, Env, String}; use crate::{ - storage::{MinterInfo, VestingInfo, VestingSchedule, VestingTokenInfo}, + storage::{VestingInfo, VestingSchedule, VestingTokenInfo}, tests::setup::{deploy_token_contract, instantiate_vesting_client}, }; use curve::{Curve, SaturatingLinear}; @@ -45,15 +45,9 @@ fn instantiate_contract_successfully() { }, ]; - let vesting_client = instantiate_vesting_client( - &env, - &admin, - vesting_token.clone(), - 10u32, - None::, - ); - + let vesting_client = instantiate_vesting_client(&env); token_client.mint(&admin, &480); + vesting_client.initialize(&admin, &vesting_token, &10u32); vesting_client.create_vesting_schedules(&vesting_schedules); assert_eq!(vesting_client.query_token_info(), vesting_token); @@ -94,15 +88,10 @@ fn instantiate_contract_without_any_vesting_balances_should_fail() { }; let vesting_schedules = vec![&env]; - let vesting_client = instantiate_vesting_client( - &env, - &admin, - vesting_token.clone(), - 10u32, - None::, - ); + let vesting_client = instantiate_vesting_client(&env); token_client.mint(&admin, &100); + vesting_client.initialize(&admin, &vesting_token, &10u32); vesting_client.create_vesting_schedules(&vesting_schedules); } @@ -138,13 +127,32 @@ fn create_schedule_panics_when_admin_has_no_tokens_to_fund() { }, ]; - let vesting_client = instantiate_vesting_client( - &env, - &admin, - vesting_token.clone(), - 10u32, - None::, - ); + let vesting_client = instantiate_vesting_client(&env); + vesting_client.initialize(&admin, &vesting_token, &10u32); vesting_client.create_vesting_schedules(&vesting_schedules); } + +#[test] +#[should_panic(expected = "Stake: Initialize: initializing contract twice is not allowed")] +fn instantiate_contract_twice_should_fail() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + + let token_client = deploy_token_contract(&env, &admin); + + let vesting_token = VestingTokenInfo { + name: String::from_str(&env, "Phoenix"), + symbol: String::from_str(&env, "PHO"), + decimals: 6, + address: token_client.address.clone(), + }; + + let vesting_client = instantiate_vesting_client(&env); + token_client.mint(&admin, &480); + vesting_client.initialize(&admin, &vesting_token, &10u32); + // initialize again + vesting_client.initialize(&admin, &vesting_token, &10u32); +} diff --git a/contracts/vesting/src/tests/minter.rs b/contracts/vesting/src/tests/minter.rs index c137c7f9f..b4a443f59 100644 --- a/contracts/vesting/src/tests/minter.rs +++ b/contracts/vesting/src/tests/minter.rs @@ -28,16 +28,12 @@ fn instantiate_contract_successfully_with_constant_curve_minter_info() { mint_capacity: 511223344, }; - let vesting_client = instantiate_vesting_client( - &env, - &admin, - vesting_token.clone(), - 10u32, - Some(minter_info), - ); + let vesting_client = instantiate_vesting_client(&env); token_client.mint(&admin, &240); + vesting_client.initialize_with_minter(&admin, &vesting_token, &10u32, &minter_info); + assert_eq!(vesting_client.query_token_info(), vesting_token); } @@ -64,14 +60,9 @@ fn mint_panics_when_over_the_cap() { mint_capacity: 100, }; - let vesting_client = instantiate_vesting_client( - &env, - &admin, - vesting_token.clone(), - 10u32, - Some(minter_info), - ); + let vesting_client = instantiate_vesting_client(&env); + vesting_client.initialize_with_minter(&admin, &vesting_token, &10u32, &minter_info); vesting_client.mint(&minter, &110i128); } @@ -106,19 +97,12 @@ fn burn_works() { }, ]; + let vesting_client = instantiate_vesting_client(&env); let minter_info = MinterInfo { address: Address::generate(&env), mint_capacity: 10_000, }; - - let vesting_client = instantiate_vesting_client( - &env, - &admin, - vesting_token.clone(), - 10u32, - Some(minter_info), - ); - + vesting_client.initialize_with_minter(&admin, &vesting_token, &10u32, &minter_info); vesting_client.create_vesting_schedules(&vesting_schedules); env.ledger().with_mut(|li| li.timestamp = 100); @@ -151,19 +135,12 @@ fn burn_should_panic_when_invalid_amount() { address: token.address.clone(), }; + let vesting_client = instantiate_vesting_client(&env); let minter_info = MinterInfo { address: Address::generate(&env), mint_capacity: 10_000, }; - - let vesting_client = instantiate_vesting_client( - &env, - &admin, - vesting_token.clone(), - 10u32, - Some(minter_info), - ); - + vesting_client.initialize_with_minter(&admin, &vesting_token, &10u32, &minter_info); vesting_client.burn(&Address::generate(&env), &0); } @@ -204,14 +181,8 @@ fn mint_works() { mint_capacity: 500, }; - let vesting_client = instantiate_vesting_client( - &env, - &admin, - vesting_token.clone(), - 10u32, - Some(minter_info), - ); - + let vesting_client = instantiate_vesting_client(&env); + vesting_client.initialize_with_minter(&admin, &vesting_token, &10u32, &minter_info.clone()); vesting_client.create_vesting_schedules(&vesting_schedules); // we start with 120 tokens minted to the contract @@ -263,13 +234,8 @@ fn mint_should_panic_when_invalid_amount() { mint_capacity: 500, }; - let vesting_client = instantiate_vesting_client( - &env, - &admin, - vesting_token.clone(), - 10u32, - Some(minter_info), - ); + let vesting_client = instantiate_vesting_client(&env); + vesting_client.initialize_with_minter(&admin, &vesting_token, &10u32, &minter_info); vesting_client.mint(&Address::generate(&env), &0); } @@ -299,13 +265,8 @@ fn mint_should_panic_when_not_authorized_to_mint() { mint_capacity: 500, }; - let vesting_client = instantiate_vesting_client( - &env, - &admin, - vesting_token.clone(), - 10u32, - Some(minter_info), - ); + let vesting_client = instantiate_vesting_client(&env); + vesting_client.initialize_with_minter(&admin, &vesting_token, &10u32, &minter_info); vesting_client.mint(&vester1, &100); } @@ -335,13 +296,8 @@ fn mint_should_panic_when_mintet_does_not_have_enough_capacity() { mint_capacity: 500, }; - let vesting_client = instantiate_vesting_client( - &env, - &admin, - vesting_token.clone(), - 10u32, - Some(minter_info), - ); + let vesting_client = instantiate_vesting_client(&env); + vesting_client.initialize_with_minter(&admin, &vesting_token, &10u32, &minter_info); vesting_client.mint(&minter, &1_500); } @@ -370,13 +326,8 @@ fn update_minter_works_correctly() { mint_capacity: 500, }; - let vesting_client = instantiate_vesting_client( - &env, - &admin, - vesting_token.clone(), - 10u32, - Some(minter_info.clone()), - ); + let vesting_client = instantiate_vesting_client(&env); + vesting_client.initialize_with_minter(&admin, &vesting_token, &10u32, &minter_info.clone()); assert_eq!(vesting_client.query_minter(), minter_info); @@ -418,13 +369,8 @@ fn update_minter_fails_when_not_authorized() { mint_capacity: 500, }; - let vesting_client = instantiate_vesting_client( - &env, - &admin, - vesting_token.clone(), - 10u32, - Some(minter_info), - ); + let vesting_client = instantiate_vesting_client(&env); + vesting_client.initialize_with_minter(&admin, &vesting_token, &10u32, &minter_info.clone()); let new_minter_info = MinterInfo { address: new_minter.clone(), @@ -458,13 +404,8 @@ fn update_minter_capacity_when_replacing_old_capacity() { mint_capacity: 50_000, }; - let vesting_client = instantiate_vesting_client( - &env, - &admin, - vesting_token.clone(), - 10u32, - Some(minter_info), - ); + let vesting_client = instantiate_vesting_client(&env); + vesting_client.initialize_with_minter(&admin, &vesting_token, &10u32, &minter_info.clone()); let new_minter_capacity = 1_000; vesting_client.update_minter_capacity(&admin, &new_minter_capacity); @@ -502,13 +443,8 @@ fn updating_minter_capacity_without_auth() { mint_capacity: 50_000, }; - let vesting_client = instantiate_vesting_client( - &env, - &admin, - vesting_token.clone(), - 10u32, - Some(minter_info), - ); + let vesting_client = instantiate_vesting_client(&env); + vesting_client.initialize_with_minter(&admin, &vesting_token, &10u32, &minter_info.clone()); vesting_client.update_minter_capacity(&Address::generate(&env), &1_000); } @@ -532,18 +468,12 @@ fn burning_more_than_balance() { address: token.address.clone(), }; + let vesting_client = instantiate_vesting_client(&env); let minter_info = MinterInfo { address: Address::generate(&env), mint_capacity: 1_000, }; - - let vesting_client = instantiate_vesting_client( - &env, - &admin, - vesting_token.clone(), - 10u32, - Some(minter_info), - ); + vesting_client.initialize_with_minter(&admin, &vesting_token, &10u32, &minter_info); // vester1 tries to burn 121 tokens vesting_client.burn(&Address::generate(&env), &121); diff --git a/contracts/vesting/src/tests/setup.rs b/contracts/vesting/src/tests/setup.rs index 2cde90da4..a1811984f 100644 --- a/contracts/vesting/src/tests/setup.rs +++ b/contracts/vesting/src/tests/setup.rs @@ -2,29 +2,11 @@ use soroban_sdk::{Address, Env}; use crate::{ contract::{Vesting, VestingClient}, - storage::{MinterInfo, VestingTokenInfo}, token_contract, }; -pub fn instantiate_vesting_client<'a>( - env: &Env, - admin: &Address, - vesting_token: VestingTokenInfo, - max_vesting_complexity: u32, - minter_info: Option, -) -> VestingClient<'a> { - VestingClient::new( - env, - &env.register( - Vesting, - ( - admin.clone(), - vesting_token, - max_vesting_complexity, - minter_info, - ), - ), - ) +pub fn instantiate_vesting_client(env: &Env) -> VestingClient { + VestingClient::new(env, &env.register(Vesting, ())) } pub fn deploy_token_contract<'a>(env: &Env, admin: &Address) -> token_contract::Client<'a> { diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 5a28d8437..d7e24c70b 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -1,4 +1,3 @@ -#!/bin/bash # Ensure the script exits on any errors set -e @@ -26,7 +25,6 @@ soroban contract optimize --wasm phoenix_pool_stable.wasm soroban contract optimize --wasm phoenix_stake.wasm soroban contract optimize --wasm phoenix_stake_rewards.wasm soroban contract optimize --wasm phoenix_multihop.wasm -soroban contract optimize --wasm phoenix_stake_rewards.wasm echo "Contracts optimized." @@ -42,16 +40,28 @@ TOKEN_ADDR1=$XLM TOKEN_ADDR2=$(soroban contract deploy \ --wasm soroban_token_contract.optimized.wasm \ --source $IDENTITY_STRING \ + --network $NETWORK) + +soroban contract invoke \ + --id $TOKEN_ADDR2 \ + --source $IDENTITY_STRING \ --network $NETWORK \ -- \ + initialize \ --admin $ADMIN_ADDRESS \ --decimal 7 \ --name PHOENIX \ --symbol PHO -) echo "PHO Token initialized." +FACTORY_ADDR=$(soroban contract deploy \ + --wasm phoenix_factory.optimized.wasm \ + --source $IDENTITY_STRING \ + --network $NETWORK) + +echo "Tokens and factory deployed." + # Sort the token addresses alphabetically if [[ "$TOKEN_ADDR1" < "$TOKEN_ADDR2" ]]; then TOKEN_ID1=$TOKEN_ADDR1 @@ -61,7 +71,7 @@ else TOKEN_ID2=$TOKEN_ADDR1 fi -echo "Install the soroban_token, multihop, phoenix_pool and phoenix_stake contracts..." +echo "Install the soroban_token, phoenix_pool and phoenix_stake contracts..." TOKEN_WASM_HASH=$(soroban contract install \ --wasm soroban_token_contract.optimized.wasm \ @@ -89,45 +99,33 @@ STAKE_REWARDS_WASM_HASH=$(soroban contract install \ --source $IDENTITY_STRING \ --network $NETWORK) -MULTIHOP_WASM_HASH=$(soroban contract install \ - --wasm phoenix_multihop.optimized.wasm \ - --source $IDENTITY_STRING \ - --network $NETWORK) - echo "Token, pair and stake contracts deployed." echo "Initialize factory..." -FACTORY_ADDR=$(soroban contract deploy \ - --wasm phoenix_factory.optimized.wasm \ +MULTIHOP=$(soroban contract install \ + --wasm phoenix_multihop.optimized.wasm \ + --source $IDENTITY_STRING \ + --network $NETWORK) + +soroban contract invoke \ + --id $FACTORY_ADDR \ --source $IDENTITY_STRING \ --network $NETWORK \ -- \ + initialize \ --admin $ADMIN_ADDRESS \ - --multihop_wasm_hash $MULTIHOP_WASM_HASH \ + --multihop_wasm_hash $MULTIHOP \ --lp_wasm_hash $PAIR_WASM_HASH \ --stable_wasm_hash $STABLE_PAIR_WASM_HASH \ --stake_wasm_hash $STAKE_WASM_HASH \ + --stake_rewards_wasm_hash $STAKE_REWARDS_WASM_HASH \ --token_wasm_hash $TOKEN_WASM_HASH \ --whitelisted_accounts "[ \"${ADMIN_ADDRESS}\" ]" \ --lp_token_decimals 7 -) echo "Factory initialized: " $FACTORY_ADDR -echo "Initialize Multihop..." -MULTIHOP_ADDR=$(soroban contract deploy \ - --wasm phoenix_multihop.optimized.wasm \ - --source $IDENTITY_STRING \ - --network $NETWORK \ - -- \ - --admin $ADMIN_ADDRESS \ - --factory $FACTORY_ADDR -) - -echo "Multihop initialized: " $MULTIHOP_ADDR - - echo "Initialize pair using the previously fetched hashes through factory..." soroban contract invoke \ @@ -138,11 +136,11 @@ soroban contract invoke \ create_liquidity_pool \ --sender $ADMIN_ADDRESS \ --lp_init_info "{ \"admin\": \"${ADMIN_ADDRESS}\", \"swap_fee_bps\": 1000, \"fee_recipient\": \"${ADMIN_ADDRESS}\", \"max_allowed_slippage_bps\": 10000, \"default_slippage_bps\": 3000, \"max_allowed_spread_bps\": 10000, \"max_referral_bps\": 5000, \"token_init_info\": { \"token_a\": \"${TOKEN_ID1}\", \"token_b\": \"${TOKEN_ID2}\" }, \"stake_init_info\": { \"min_bond\": \"100\", \"min_reward\": \"100\", \"max_distributions\": 3, \"manager\": \"${ADMIN_ADDRESS}\", \"max_complexity\": 7 } }" \ + --default_slippage_bps 3000 \ + --max_allowed_fee_bps 10000 \ --share_token_name "XLMPHOST" \ --share_token_symbol "XPST" \ - --pool_type 0 \ - --default_slippage_bps 3000 \ - --max_allowed_fee_bps 10000 + --pool_type 0 echo "Query XLM/PHO pair address..." @@ -200,14 +198,18 @@ echo "#############################" TOKEN_ADDR1=$(soroban contract deploy \ --wasm soroban_token_contract.optimized.wasm \ --source $IDENTITY_STRING \ + --network $NETWORK) + +soroban contract invoke \ + --id $TOKEN_ADDR1 \ + --source $IDENTITY_STRING \ --network $NETWORK \ -- \ + initialize \ --admin $ADMIN_ADDRESS \ --decimal 7 \ --name USDC \ --symbol USDC -) - echo "USDC Token initialized." @@ -276,7 +278,7 @@ echo "Liquidity provided." echo "Query stake contract address..." STAKE_ADDR2=$(soroban contract invoke \ - --id $PAIR_ADDR2 \ + --id $PAIR_ADDR \ --source $IDENTITY_STRING \ --network $NETWORK --fee 10000000 \ -- \ @@ -293,155 +295,6 @@ soroban contract invoke \ echo "Tokens bonded." -echo "Starting the deployment of stable pool..." - -echo "Deploying GBPx and EURc ..." - -STABLE_TOKEN_A=$( -soroban contract deploy \ - --wasm soroban_token_contract.optimized.wasm \ - --source $IDENTITY_STRING \ - --network $NETWORK \ - -- \ - --admin $ADMIN_ADDRESS \ - --decimal 7 \ - --name GBPCoin \ - --symbol GBPx -) - -STABLE_TOKEN_B=$( -soroban contract deploy \ - --wasm soroban_token_contract.optimized.wasm \ - --source $IDENTITY_STRING \ - --network $NETWORK \ - -- \ - --admin $ADMIN_ADDRESS \ - --decimal 7 \ - --name EuroCoin \ - --symbol EURc -) - -if [[ "$STABLE_TOKEN_A" < "$STABLE_TOKEN_B" ]]; then - STABLE_TOKEN_ID1=$STABLE_TOKEN_A - STABLE_TOKEN_ID2=$STABLE_TOKEN_B -else - STABLE_TOKEN_ID1=$STABLE_TOKEN_B - STABLE_TOKEN_ID2=$STABLE_TOKEN_A -fi - -echo "Minting GBPx and EURc..." - -soroban contract invoke \ - --id $STABLE_TOKEN_ID1 \ - --source $IDENTITY_STRING \ - --network $NETWORK \ - -- \ - mint --to $ADMIN_ADDRESS --amount 100000000000 - -soroban contract invoke \ - --id $STABLE_TOKEN_ID2 \ - --source $IDENTITY_STRING \ - --network $NETWORK \ - -- \ - mint --to $ADMIN_ADDRESS --amount 100000000000 - -echo "Deploy GBPx/EURc stable pool ..." - -soroban contract invoke \ - --id $FACTORY_ADDR \ - --source $IDENTITY_STRING \ - --network $NETWORK \ - -- \ - create_liquidity_pool \ - --sender $ADMIN_ADDRESS \ - --lp_init_info "{ \ - \"admin\": \"${ADMIN_ADDRESS}\", \ - \"swap_fee_bps\": 1000, \ - \"fee_recipient\": \"${ADMIN_ADDRESS}\", \ - \"max_allowed_slippage_bps\": 10000, \ - \"default_slippage_bps\": 3000, \ - \"max_allowed_spread_bps\": 10000, \ - \"max_referral_bps\": 5000, \ - \"token_init_info\": { \ - \"token_a\": \"${STABLE_TOKEN_ID1}\", \ - \"token_b\": \"${STABLE_TOKEN_ID2}\" \ - }, \ - \"stake_init_info\": { \ - \"min_bond\": \"100\", \ - \"min_reward\": \"100\", \ - \"max_distributions\": \"3\", \ - \"manager\": \"${ADMIN_ADDRESS}\", \ - \"max_complexity\": 7 \ - } \ - }" \ - --default_slippage_bps 3000 \ - --max_allowed_fee_bps 10000 \ - --share_token_name "GBPEURCST" \ - --share_token_symbol "GEST" \ - --pool_type 1 \ - --amp 50 - -echo "Query GBPx/EURc pair address..." - -STABLE_PAIR_ADDR=$(soroban contract invoke \ - --id $FACTORY_ADDR \ - --source $IDENTITY_STRING \ - --network $NETWORK --fee 100 \ - -- \ - query_pools | jq -r '.[2]') - -echo "Providing liquidity to stable pool: " $STABLE_PAIR_ADDR - -# temporary using 2 decimals zeros less, when the liquidity pool is fixed we can use regular numbers again -soroban contract invoke \ - --id $STABLE_PAIR_ADDR \ - --source $IDENTITY_STRING \ - --network $NETWORK --fee 10000000 \ - -- \ - provide_liquidity --sender $ADMIN_ADDRESS --desired_a 20000000 --desired_b 20000000 - -echo "Liquidity provided." - -echo "#############################" - -echo "Deploy and initialize stake_rewards contracts..." - -MAX_COMPLEXITY=7 -MIN_REWARD=100 -MIN_BOND=100 - -echo "Deploying stake_rewards for the XLM/PHO Stake Contract ($STAKE_ADDR)..." -STAKING_REWARDS_XLM_PHO_ADDR=$(soroban contract deploy \ - --wasm phoenix_stake_rewards.optimized.wasm \ - --source $IDENTITY_STRING \ - --network $NETWORK \ - -- \ - --admin "$ADMIN_ADDRESS" \ - --staking_contract "$STAKE_ADDR" \ - --reward_token "$TOKEN_ADDR2" \ - --max_complexity "$MAX_COMPLEXITY" \ - --min_reward "$MIN_REWARD" \ - --min_bond "$MIN_BOND" -) - -echo "Staking Rewards Contract for XLM/PHO deployed at address: $STAKING_REWARDS_XLM_PHO_ADDR" - -echo "Deploying staking_rewards for the PHO/USDC Stake Contract ($STAKE_ADDR2)..." -STAKING_REWARDS_PHO_USDC_ADDR=$(soroban contract deploy \ - --wasm phoenix_stake_rewards.optimized.wasm \ - --source $IDENTITY_STRING \ - --network $NETWORK \ - -- \ - --admin "$ADMIN_ADDRESS" \ - --staking_contract "$STAKE_ADDR2" \ - --reward_token "$TOKEN_ADDR2" \ - --max_complexity "$MAX_COMPLEXITY" \ - --min_reward "$MIN_REWARD" \ - --min_bond "$MIN_BOND" -) - -echo "Staking Rewards Contract for PHO/USDC deployed at address: $STAKING_REWARDS_PHO_USDC_ADDR" - echo "#############################" echo "Initialization complete!" @@ -452,8 +305,6 @@ echo "XLM/PHO Pair Contract address: $PAIR_ADDR" echo "XLM/PHO Stake Contract address: $STAKE_ADDR" echo "PHO/USDC Pair Contract address: $PAIR_ADDR2" echo "PHO/USDC Stake Contract address: $STAKE_ADDR2" -echo "GBPx/EURc Pair Contract address: $STABLE_PAIR_ADDR" echo "Factory Contract address: $FACTORY_ADDR" -echo "Multihop Contract address: $MULTIHOP_ADDR" -echo "Staking Rewards Contract for XLM/PHO address: $STAKING_REWARDS_XLM_PHO_ADDR" -echo "Staking Rewards Contract for PHO/USDC address: $STAKING_REWARDS_PHO_USDC_ADDR" +echo "Multihop Contract address: $MULTIHOP" +