From b14981ca13b1a0e1bbe893d91f7479e449c7a17c Mon Sep 17 00:00:00 2001 From: coderipper Date: Fri, 8 Nov 2024 11:16:01 -0300 Subject: [PATCH 1/4] hodl to fixapr --- apps/contracts/Cargo.lock | 8 + .../contracts/strategies/fixed_apr/Cargo.toml | 18 +++ apps/contracts/strategies/fixed_apr/Makefile | 17 +++ .../strategies/fixed_apr/src/balance.rs | 43 ++++++ .../contracts/strategies/fixed_apr/src/lib.rs | 140 ++++++++++++++++++ .../strategies/fixed_apr/src/storage.rs | 37 +++++ .../strategies/fixed_apr/src/test.rs | 63 ++++++++ .../strategies/fixed_apr/src/test/deposit.rs | 79 ++++++++++ .../strategies/fixed_apr/src/test/events.rs | 6 + .../fixed_apr/src/test/initialize.rs | 21 +++ .../strategies/fixed_apr/src/test/withdraw.rs | 5 + 11 files changed, 437 insertions(+) create mode 100644 apps/contracts/strategies/fixed_apr/Cargo.toml create mode 100644 apps/contracts/strategies/fixed_apr/Makefile create mode 100644 apps/contracts/strategies/fixed_apr/src/balance.rs create mode 100644 apps/contracts/strategies/fixed_apr/src/lib.rs create mode 100644 apps/contracts/strategies/fixed_apr/src/storage.rs create mode 100644 apps/contracts/strategies/fixed_apr/src/test.rs create mode 100644 apps/contracts/strategies/fixed_apr/src/test/deposit.rs create mode 100644 apps/contracts/strategies/fixed_apr/src/test/events.rs create mode 100644 apps/contracts/strategies/fixed_apr/src/test/initialize.rs create mode 100644 apps/contracts/strategies/fixed_apr/src/test/withdraw.rs diff --git a/apps/contracts/Cargo.lock b/apps/contracts/Cargo.lock index 889ab1d3..c4c169b0 100644 --- a/apps/contracts/Cargo.lock +++ b/apps/contracts/Cargo.lock @@ -439,6 +439,14 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +[[package]] +name = "fixed_apr_strategy" +version = "1.0.0" +dependencies = [ + "defindex-strategy-core", + "soroban-sdk", +] + [[package]] name = "fnv" version = "1.0.7" diff --git a/apps/contracts/strategies/fixed_apr/Cargo.toml b/apps/contracts/strategies/fixed_apr/Cargo.toml new file mode 100644 index 00000000..add447c2 --- /dev/null +++ b/apps/contracts/strategies/fixed_apr/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "fixed_apr_strategy" +version = { workspace = true } +authors = ["coderipper "] +license = { workspace = true } +edition = { workspace = true } +publish = false +repository = { workspace = true } + +[lib] +crate-type = ["cdylib"] + +[dependencies] +soroban-sdk = { workspace = true } +defindex-strategy-core = { workspace = true } + +[dev-dependencies] +soroban-sdk = { workspace = true, features = ["testutils"] } diff --git a/apps/contracts/strategies/fixed_apr/Makefile b/apps/contracts/strategies/fixed_apr/Makefile new file mode 100644 index 00000000..480ff124 --- /dev/null +++ b/apps/contracts/strategies/fixed_apr/Makefile @@ -0,0 +1,17 @@ +default: build + +all: test + +test: build + cargo test + +build: + cargo build --target wasm32-unknown-unknown --release + soroban contract optimize --wasm ../../target/wasm32-unknown-unknown/release/fixed_apr_strategy.wasm + @rm ../../target/wasm32-unknown-unknown/release/fixed_apr_strategy.wasm + +fmt: + cargo fmt --all --check + +clean: + cargo clean \ No newline at end of file diff --git a/apps/contracts/strategies/fixed_apr/src/balance.rs b/apps/contracts/strategies/fixed_apr/src/balance.rs new file mode 100644 index 00000000..1f5751be --- /dev/null +++ b/apps/contracts/strategies/fixed_apr/src/balance.rs @@ -0,0 +1,43 @@ +use soroban_sdk::{Address, Env}; + +use crate::storage::{DataKey, INSTANCE_BUMP_AMOUNT, INSTANCE_LIFETIME_THRESHOLD}; +use crate::StrategyError; + +pub fn read_balance(e: &Env, addr: Address) -> i128 { + let key = DataKey::Balance(addr); + if let Some(balance) = e.storage().persistent().get::(&key) { + e.storage() + .persistent() + .extend_ttl(&key, INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT); + balance + } else { + 0 + } +} + +fn write_balance(e: &Env, addr: Address, amount: i128) { + let key = DataKey::Balance(addr); + e.storage().persistent().set(&key, &amount); + e.storage() + .persistent() + .extend_ttl(&key, INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT); +} + +pub fn receive_balance(e: &Env, addr: Address, amount: i128) { + let balance = read_balance(e, addr.clone()); + + let new_balance = balance.checked_add(amount) + .expect("Integer overflow occurred while adding balance."); + + write_balance(e, addr, new_balance); +} + +pub fn spend_balance(e: &Env, addr: Address, amount: i128) -> Result<(), StrategyError> { + + let balance = read_balance(e, addr.clone()); + if balance < amount { + return Err(StrategyError::InsufficientBalance); + } + write_balance(e, addr, balance - amount); + Ok(()) +} diff --git a/apps/contracts/strategies/fixed_apr/src/lib.rs b/apps/contracts/strategies/fixed_apr/src/lib.rs new file mode 100644 index 00000000..d0ba1719 --- /dev/null +++ b/apps/contracts/strategies/fixed_apr/src/lib.rs @@ -0,0 +1,140 @@ +#![no_std] +use soroban_sdk::{ + contract, + contractimpl, + Address, + Env, + String, + token::Client as TokenClient, + Val, + Vec}; + +mod balance; +mod storage; + +use balance::{ + read_balance, + receive_balance, + spend_balance}; + +use storage::{ + extend_instance_ttl, + get_underlying_asset, + is_initialized, + set_initialized, + set_underlying_asset +}; + +pub use defindex_strategy_core::{ + DeFindexStrategyTrait, + StrategyError, + event}; + +pub fn check_nonnegative_amount(amount: i128) -> Result<(), StrategyError> { + if amount < 0 { + Err(StrategyError::NegativeNotAllowed) + } else { + Ok(()) + } +} + +fn check_initialized(e: &Env) -> Result<(), StrategyError> { + if is_initialized(e) { + Ok(()) + } else { + Err(StrategyError::NotInitialized) + } +} + +const STARETEGY_NAME: &str = "FixAprStrategy"; + +#[contract] +struct FixAprStrategy; + +#[contractimpl] +impl DeFindexStrategyTrait for FixAprStrategy { + fn initialize( + e: Env, + asset: Address, + _init_args: Vec, + ) -> Result<(), StrategyError> { + if is_initialized(&e) { + return Err(StrategyError::AlreadyInitialized); + } + + set_initialized(&e); + set_underlying_asset(&e, &asset); + + event::emit_initialize(&e, String::from_str(&e, STARETEGY_NAME), asset); + extend_instance_ttl(&e); + Ok(()) + } + + fn asset(e: Env) -> Result { + check_initialized(&e)?; + extend_instance_ttl(&e); + + Ok(get_underlying_asset(&e)) + } + + fn deposit( + e: Env, + amount: i128, + from: Address, + ) -> Result<(), StrategyError> { + check_initialized(&e)?; + check_nonnegative_amount(amount)?; + extend_instance_ttl(&e); + from.require_auth(); + + let contract_address = e.current_contract_address(); + + let underlying_asset = get_underlying_asset(&e); + TokenClient::new(&e, &underlying_asset).transfer(&from, &contract_address, &amount); + + receive_balance(&e, from.clone(), amount); + event::emit_deposit(&e, String::from_str(&e, STARETEGY_NAME), amount, from); + + Ok(()) + } + + fn harvest(e: Env, from: Address) -> Result<(), StrategyError> { + check_initialized(&e)?; + extend_instance_ttl(&e); + + event::emit_harvest(&e, String::from_str(&e, STARETEGY_NAME), 0i128, from); + Ok(()) + } + + fn withdraw( + e: Env, + amount: i128, + from: Address, + ) -> Result { + from.require_auth(); + check_initialized(&e)?; + check_nonnegative_amount(amount)?; + extend_instance_ttl(&e); + + spend_balance(&e, from.clone(), amount)?; + + let contract_address = e.current_contract_address(); + let underlying_asset = get_underlying_asset(&e); + TokenClient::new(&e, &underlying_asset).transfer(&contract_address, &from, &amount); + event::emit_withdraw(&e, String::from_str(&e, STARETEGY_NAME), amount, from); + + Ok(amount) + } + + fn balance( + e: Env, + from: Address, + ) -> Result { + check_initialized(&e)?; + extend_instance_ttl(&e); + + Ok(read_balance(&e, from)) + } +} + +mod test; \ No newline at end of file diff --git a/apps/contracts/strategies/fixed_apr/src/storage.rs b/apps/contracts/strategies/fixed_apr/src/storage.rs new file mode 100644 index 00000000..6fd7b39f --- /dev/null +++ b/apps/contracts/strategies/fixed_apr/src/storage.rs @@ -0,0 +1,37 @@ +use soroban_sdk::{contracttype, Env, Address}; + +#[derive(Clone)] +#[contracttype] + +pub enum DataKey { + Initialized, + UnderlyingAsset, + Balance(Address) +} + +const DAY_IN_LEDGERS: u32 = 17280; +pub const INSTANCE_BUMP_AMOUNT: u32 = 30 * DAY_IN_LEDGERS; +pub const INSTANCE_LIFETIME_THRESHOLD: u32 = INSTANCE_BUMP_AMOUNT - DAY_IN_LEDGERS; + +pub fn extend_instance_ttl(e: &Env) { + e.storage() + .instance() + .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT); +} + +pub fn set_initialized(e: &Env) { + e.storage().instance().set(&DataKey::Initialized, &true); +} + +pub fn is_initialized(e: &Env) -> bool { + e.storage().instance().has(&DataKey::Initialized) +} + +// Underlying asset +pub fn set_underlying_asset(e: &Env, address: &Address) { + e.storage().instance().set(&DataKey::UnderlyingAsset, &address); +} + +pub fn get_underlying_asset(e: &Env) -> Address { + e.storage().instance().get(&DataKey::UnderlyingAsset).unwrap() +} \ No newline at end of file diff --git a/apps/contracts/strategies/fixed_apr/src/test.rs b/apps/contracts/strategies/fixed_apr/src/test.rs new file mode 100644 index 00000000..6b759712 --- /dev/null +++ b/apps/contracts/strategies/fixed_apr/src/test.rs @@ -0,0 +1,63 @@ +#![cfg(test)] +use crate::{FixAprStrategy, FixAprStrategyClient, StrategyError}; + +use soroban_sdk::token::{TokenClient, StellarAssetClient}; + +use soroban_sdk::{ + Env, + Address, + testutils::Address as _, +}; + +// Base Strategy Contract +fn create_fixapr_strategy<'a>(e: &Env) -> FixAprStrategyClient<'a> { + FixAprStrategyClient::new(e, &e.register_contract(None, FixAprStrategy {})) +} + +// Create Test Token +pub(crate) fn create_token_contract<'a>(e: &Env, admin: &Address) -> TokenClient<'a> { + TokenClient::new(e, &e.register_stellar_asset_contract_v2(admin.clone()).address()) +} + +pub struct FixAprStrategyTest<'a> { + env: Env, + strategy: FixAprStrategyClient<'a>, + token: TokenClient<'a>, + user: Address, +} + +impl<'a> FixAprStrategyTest<'a> { + fn setup() -> Self { + + let env = Env::default(); + env.mock_all_auths(); + + let strategy = create_fixapr_strategy(&env); + let admin = Address::generate(&env); + let token = create_token_contract(&env, &admin); + let user = Address::generate(&env); + + // Mint 1,000,000,000 to user + StellarAssetClient::new(&env, &token.address).mint(&user, &1_000_000_000); + + FixAprStrategyTest { + env, + strategy, + token, + user + } + } + + // pub(crate) fn generate_random_users(e: &Env, users_count: u32) -> vec::Vec
{ + // let mut users = vec![]; + // for _c in 0..users_count { + // users.push(Address::generate(e)); + // } + // users + // } +} + +mod initialize; +mod deposit; +mod events; +mod withdraw; \ No newline at end of file diff --git a/apps/contracts/strategies/fixed_apr/src/test/deposit.rs b/apps/contracts/strategies/fixed_apr/src/test/deposit.rs new file mode 100644 index 00000000..3267f974 --- /dev/null +++ b/apps/contracts/strategies/fixed_apr/src/test/deposit.rs @@ -0,0 +1,79 @@ +use crate::test::HodlStrategyTest; +use crate::test::StrategyError; +use soroban_sdk::{IntoVal, Vec, Val}; + +// test deposit with negative amount +#[test] +fn deposit_with_negative_amount() { + let test = HodlStrategyTest::setup(); + let init_fn_args: Vec = (0,).into_val(&test.env); + test.strategy.initialize(&test.token.address, &init_fn_args); + + let amount = -123456; + + let result = test.strategy.try_deposit(&amount, &test.user); + assert_eq!(result, Err(Ok(StrategyError::NegativeNotAllowed))); +} + +// check auth +#[test] +fn deposit_mock_auths() { + todo!() +} + +#[test] +fn deposit_and_withdrawal_flow() { + let test = HodlStrategyTest::setup(); + // let users = HodlStrategyTest::generate_random_users(&test.env, 1); + + // try deposit should return NotInitialized error before being initialize + + let result = test.strategy.try_deposit(&10_000_000, &test.user); + assert_eq!(result, Err(Ok(StrategyError::NotInitialized))); + + // initialize + let init_fn_args: Vec = (0,).into_val(&test.env); + test.strategy.initialize(&test.token.address, &init_fn_args); + + // Initial user token balance + let balance = test.token.balance(&test.user); + + let amount = 123456; + + // Deposit amount of token from the user to the strategy + test.strategy.deposit(&amount, &test.user); + + let balance_after_deposit = test.token.balance(&test.user); + assert_eq!(balance_after_deposit, balance - amount); + + // Reading strategy balance + let strategy_balance_after_deposit = test.token.balance(&test.strategy.address); + assert_eq!(strategy_balance_after_deposit, amount); + + // Reading user balance on strategy contract + let user_balance_on_strategy = test.strategy.balance(&test.user); + assert_eq!(user_balance_on_strategy, amount); + + + let amount_to_withdraw = 100_000; + // Withdrawing token from the strategy to user + test.strategy.withdraw(&amount_to_withdraw, &test.user); + + // Reading user balance in token + let balance = test.token.balance(&test.user); + assert_eq!(balance, balance_after_deposit + amount_to_withdraw); + + // Reading strategy balance in token + let balance = test.token.balance(&test.strategy.address); + assert_eq!(balance, amount - amount_to_withdraw); + + // Reading user balance on strategy contract + let user_balance = test.strategy.balance(&test.user); + assert_eq!(user_balance, amount - amount_to_withdraw); + + // now we will want to withdraw more of the remaining balance + let amount_to_withdraw = 200_000; + let result = test.strategy.try_withdraw(&amount_to_withdraw, &test.user); + assert_eq!(result, Err(Ok(StrategyError::InsufficientBalance))); + +} \ No newline at end of file diff --git a/apps/contracts/strategies/fixed_apr/src/test/events.rs b/apps/contracts/strategies/fixed_apr/src/test/events.rs new file mode 100644 index 00000000..239a9bd1 --- /dev/null +++ b/apps/contracts/strategies/fixed_apr/src/test/events.rs @@ -0,0 +1,6 @@ +// TODO: Write tests for events + +#[test] +fn test_events() { + todo!() +} \ No newline at end of file diff --git a/apps/contracts/strategies/fixed_apr/src/test/initialize.rs b/apps/contracts/strategies/fixed_apr/src/test/initialize.rs new file mode 100644 index 00000000..41037473 --- /dev/null +++ b/apps/contracts/strategies/fixed_apr/src/test/initialize.rs @@ -0,0 +1,21 @@ +// Cannot Initialize twice +extern crate std; +use soroban_sdk::{IntoVal, Vec, Val}; +use crate::test::HodlStrategyTest; +use crate::test::StrategyError; + +#[test] +fn cannot_initialize_twice() { + let test = HodlStrategyTest::setup(); + + let init_fn_args: Vec = (0,).into_val(&test.env); + + test.strategy.initialize(&test.token.address, &init_fn_args); + let result = test.strategy.try_initialize(&test.token.address , &init_fn_args); + assert_eq!(result, Err(Ok(StrategyError::AlreadyInitialized))); + + // get asset should return underlying asset + + let underlying_asset = test.strategy.asset(); + assert_eq!(underlying_asset, test.token.address); +} \ No newline at end of file diff --git a/apps/contracts/strategies/fixed_apr/src/test/withdraw.rs b/apps/contracts/strategies/fixed_apr/src/test/withdraw.rs new file mode 100644 index 00000000..dd8aa9d5 --- /dev/null +++ b/apps/contracts/strategies/fixed_apr/src/test/withdraw.rs @@ -0,0 +1,5 @@ + +#[test] +fn withdraw() { + todo!() +} \ No newline at end of file From 7fb3aaf11153c781b38603d38124974526392fd8 Mon Sep 17 00:00:00 2001 From: coderipper Date: Fri, 8 Nov 2024 14:33:04 -0300 Subject: [PATCH 2/4] apr strategy tested --- .../strategies/fixed_apr/src/constants.rs | 2 + .../contracts/strategies/fixed_apr/src/lib.rs | 103 +++++++++---- .../strategies/fixed_apr/src/storage.rs | 26 +++- .../strategies/fixed_apr/src/test.rs | 29 ++-- .../strategies/fixed_apr/src/test/deposit.rs | 126 +++++++++------- .../strategies/fixed_apr/src/test/events.rs | 6 - .../strategies/fixed_apr/src/test/harvest.rs | 72 +++++++++ .../fixed_apr/src/test/initialize.rs | 42 +++++- .../strategies/fixed_apr/src/test/withdraw.rs | 140 +++++++++++++++++- .../strategies/fixed_apr/src/yield_balance.rs | 43 ++++++ 10 files changed, 479 insertions(+), 110 deletions(-) create mode 100644 apps/contracts/strategies/fixed_apr/src/constants.rs delete mode 100644 apps/contracts/strategies/fixed_apr/src/test/events.rs create mode 100644 apps/contracts/strategies/fixed_apr/src/test/harvest.rs create mode 100644 apps/contracts/strategies/fixed_apr/src/yield_balance.rs diff --git a/apps/contracts/strategies/fixed_apr/src/constants.rs b/apps/contracts/strategies/fixed_apr/src/constants.rs new file mode 100644 index 00000000..1428e1b8 --- /dev/null +++ b/apps/contracts/strategies/fixed_apr/src/constants.rs @@ -0,0 +1,2 @@ +pub(crate) const MAX_BPS: i128 = 10_000; +pub(crate) const SECONDS_PER_YEAR: i128 = 31_536_000; diff --git a/apps/contracts/strategies/fixed_apr/src/lib.rs b/apps/contracts/strategies/fixed_apr/src/lib.rs index d0ba1719..a5474972 100644 --- a/apps/contracts/strategies/fixed_apr/src/lib.rs +++ b/apps/contracts/strategies/fixed_apr/src/lib.rs @@ -1,34 +1,22 @@ #![no_std] +use constants::{MAX_BPS, SECONDS_PER_YEAR}; use soroban_sdk::{ - contract, - contractimpl, - Address, - Env, - String, - token::Client as TokenClient, - Val, - Vec}; + contract, contractimpl, token::Client as TokenClient, Address, Env, IntoVal, String, Val, Vec +}; mod balance; +mod constants; mod storage; +mod yield_balance; -use balance::{ - read_balance, - receive_balance, - spend_balance}; - +use balance::{read_balance, receive_balance, spend_balance}; use storage::{ - extend_instance_ttl, - get_underlying_asset, - is_initialized, - set_initialized, - set_underlying_asset + extend_instance_ttl, get_underlying_asset, is_initialized, set_initialized, + set_underlying_asset, set_apr, get_apr, set_last_harvest_time, get_last_harvest_time, }; -pub use defindex_strategy_core::{ - DeFindexStrategyTrait, - StrategyError, - event}; +pub use defindex_strategy_core::{DeFindexStrategyTrait, StrategyError, event}; +use yield_balance::{read_yield, receive_yield, spend_yield}; pub fn check_nonnegative_amount(amount: i128) -> Result<(), StrategyError> { if amount < 0 { @@ -46,7 +34,7 @@ fn check_initialized(e: &Env) -> Result<(), StrategyError> { } } -const STARETEGY_NAME: &str = "FixAprStrategy"; +const STRATEGY_NAME: &str = "FixAprStrategy"; #[contract] struct FixAprStrategy; @@ -56,16 +44,26 @@ impl DeFindexStrategyTrait for FixAprStrategy { fn initialize( e: Env, asset: Address, - _init_args: Vec, + init_args: Vec, ) -> Result<(), StrategyError> { if is_initialized(&e) { return Err(StrategyError::AlreadyInitialized); } + // Extract APR from `init_args`, assumed to be the first argument + let apr_bps: u32 = init_args.get(0).ok_or(StrategyError::InvalidArgument)?.into_val(&e); + let caller: Address = init_args.get(1).ok_or(StrategyError::InvalidArgument)?.into_val(&e); + let amount: i128 = init_args.get(2).ok_or(StrategyError::InvalidArgument)?.into_val(&e); + set_initialized(&e); set_underlying_asset(&e, &asset); + set_apr(&e, apr_bps); - event::emit_initialize(&e, String::from_str(&e, STARETEGY_NAME), asset); + // Should transfer tokens from the caller to the contract + caller.require_auth(); + TokenClient::new(&e, &asset).transfer(&caller, &e.current_contract_address(), &amount); + + event::emit_initialize(&e, String::from_str(&e, STRATEGY_NAME), asset); extend_instance_ttl(&e); Ok(()) } @@ -73,7 +71,6 @@ impl DeFindexStrategyTrait for FixAprStrategy { fn asset(e: Env) -> Result { check_initialized(&e)?; extend_instance_ttl(&e); - Ok(get_underlying_asset(&e)) } @@ -87,13 +84,16 @@ impl DeFindexStrategyTrait for FixAprStrategy { extend_instance_ttl(&e); from.require_auth(); + update_yield_balance(&e, &from); + let contract_address = e.current_contract_address(); - let underlying_asset = get_underlying_asset(&e); TokenClient::new(&e, &underlying_asset).transfer(&from, &contract_address, &amount); receive_balance(&e, from.clone(), amount); - event::emit_deposit(&e, String::from_str(&e, STARETEGY_NAME), amount, from); + + set_last_harvest_time(&e, e.ledger().timestamp(), from.clone()); + event::emit_deposit(&e, String::from_str(&e, STRATEGY_NAME), amount, from); Ok(()) } @@ -101,8 +101,19 @@ impl DeFindexStrategyTrait for FixAprStrategy { fn harvest(e: Env, from: Address) -> Result<(), StrategyError> { check_initialized(&e)?; extend_instance_ttl(&e); + + let yield_balance = update_yield_balance(&e, &from); + + if yield_balance == 0 { + return Ok(()); + } + + // Transfer the reward tokens to the user's balance + spend_yield(&e, from.clone(), yield_balance)?; + receive_balance(&e, from.clone(), yield_balance); - event::emit_harvest(&e, String::from_str(&e, STARETEGY_NAME), 0i128, from); + event::emit_harvest(&e, String::from_str(&e, STRATEGY_NAME), yield_balance, from); + Ok(()) } @@ -121,7 +132,7 @@ impl DeFindexStrategyTrait for FixAprStrategy { let contract_address = e.current_contract_address(); let underlying_asset = get_underlying_asset(&e); TokenClient::new(&e, &underlying_asset).transfer(&contract_address, &from, &amount); - event::emit_withdraw(&e, String::from_str(&e, STARETEGY_NAME), amount, from); + event::emit_withdraw(&e, String::from_str(&e, STRATEGY_NAME), amount, from); Ok(amount) } @@ -132,9 +143,39 @@ impl DeFindexStrategyTrait for FixAprStrategy { ) -> Result { check_initialized(&e)?; extend_instance_ttl(&e); - Ok(read_balance(&e, from)) } } + +fn calculate_yield(user_balance: i128, apr: u32, time_elapsed: u64) -> i128 { + // Calculate yield based on the APR, time elapsed, and user's balance + let seconds_per_year = SECONDS_PER_YEAR; + let apr_bps = apr as i128; + let time_elapsed_i128 = time_elapsed as i128; + + (user_balance * apr_bps * time_elapsed_i128) / (seconds_per_year * MAX_BPS) +} + +fn update_yield_balance(e: &Env, from: &Address) -> i128 { + let apr = get_apr(e); + let last_harvest = get_last_harvest_time(e, from.clone()); + let time_elapsed = e.ledger().timestamp().saturating_sub(last_harvest); + + if time_elapsed == 0 { + return 0; + } + + let user_balance = read_balance(e, from.clone()); + let reward_amount = calculate_yield(user_balance, apr, time_elapsed); + + if reward_amount == 0 { + return 0; + } + + receive_yield(e, from.clone(), reward_amount); + set_last_harvest_time(e, e.ledger().timestamp(), from.clone()); + read_yield(e, from.clone()) +} + mod test; \ No newline at end of file diff --git a/apps/contracts/strategies/fixed_apr/src/storage.rs b/apps/contracts/strategies/fixed_apr/src/storage.rs index 6fd7b39f..3df9aec5 100644 --- a/apps/contracts/strategies/fixed_apr/src/storage.rs +++ b/apps/contracts/strategies/fixed_apr/src/storage.rs @@ -1,12 +1,14 @@ -use soroban_sdk::{contracttype, Env, Address}; +use soroban_sdk::{contracttype, Address, Env}; #[derive(Clone)] #[contracttype] - pub enum DataKey { Initialized, UnderlyingAsset, - Balance(Address) + Balance(Address), + YieldBalance(Address), + Apr, + LastHarvestTime(Address), } const DAY_IN_LEDGERS: u32 = 17280; @@ -34,4 +36,22 @@ pub fn set_underlying_asset(e: &Env, address: &Address) { pub fn get_underlying_asset(e: &Env) -> Address { e.storage().instance().get(&DataKey::UnderlyingAsset).unwrap() +} + +// Apr +pub fn set_apr(e: &Env, apr: u32) { + e.storage().instance().set(&DataKey::Apr, &apr); +} + +pub fn get_apr(e: &Env) -> u32 { + e.storage().instance().get(&DataKey::Apr).unwrap() +} + +// Last harvest time +pub fn set_last_harvest_time(e: &Env, timestamp: u64, from: Address) { + e.storage().instance().set(&DataKey::LastHarvestTime(from), ×tamp); +} + +pub fn get_last_harvest_time(e: &Env, from: Address) -> u64 { + e.storage().instance().get(&DataKey::LastHarvestTime(from)).unwrap_or(0) } \ No newline at end of file diff --git a/apps/contracts/strategies/fixed_apr/src/test.rs b/apps/contracts/strategies/fixed_apr/src/test.rs index 6b759712..3885aeb8 100644 --- a/apps/contracts/strategies/fixed_apr/src/test.rs +++ b/apps/contracts/strategies/fixed_apr/src/test.rs @@ -1,7 +1,8 @@ #![cfg(test)] +extern crate std; use crate::{FixAprStrategy, FixAprStrategyClient, StrategyError}; -use soroban_sdk::token::{TokenClient, StellarAssetClient}; +use soroban_sdk::token::TokenClient; use soroban_sdk::{ Env, @@ -9,6 +10,8 @@ use soroban_sdk::{ testutils::Address as _, }; +use std::vec as stdvec; + // Base Strategy Contract fn create_fixapr_strategy<'a>(e: &Env) -> FixAprStrategyClient<'a> { FixAprStrategyClient::new(e, &e.register_contract(None, FixAprStrategy {})) @@ -23,7 +26,7 @@ pub struct FixAprStrategyTest<'a> { env: Env, strategy: FixAprStrategyClient<'a>, token: TokenClient<'a>, - user: Address, + strategy_admin: Address, } impl<'a> FixAprStrategyTest<'a> { @@ -35,29 +38,27 @@ impl<'a> FixAprStrategyTest<'a> { let strategy = create_fixapr_strategy(&env); let admin = Address::generate(&env); let token = create_token_contract(&env, &admin); - let user = Address::generate(&env); - // Mint 1,000,000,000 to user - StellarAssetClient::new(&env, &token.address).mint(&user, &1_000_000_000); + let strategy_admin = Address::generate(&env); FixAprStrategyTest { env, strategy, token, - user + strategy_admin } } - // pub(crate) fn generate_random_users(e: &Env, users_count: u32) -> vec::Vec
{ - // let mut users = vec![]; - // for _c in 0..users_count { - // users.push(Address::generate(e)); - // } - // users - // } + pub(crate) fn generate_random_users(e: &Env, users_count: u32) -> stdvec::Vec
{ + let mut users = stdvec![]; + for _c in 0..users_count { + users.push(Address::generate(e)); + } + users + } } mod initialize; mod deposit; -mod events; +mod harvest; mod withdraw; \ No newline at end of file diff --git a/apps/contracts/strategies/fixed_apr/src/test/deposit.rs b/apps/contracts/strategies/fixed_apr/src/test/deposit.rs index 3267f974..6a68c39e 100644 --- a/apps/contracts/strategies/fixed_apr/src/test/deposit.rs +++ b/apps/contracts/strategies/fixed_apr/src/test/deposit.rs @@ -1,79 +1,103 @@ -use crate::test::HodlStrategyTest; +use crate::test::FixAprStrategyTest; use crate::test::StrategyError; +use soroban_sdk::token::StellarAssetClient; +use soroban_sdk::vec; use soroban_sdk::{IntoVal, Vec, Val}; // test deposit with negative amount #[test] fn deposit_with_negative_amount() { - let test = HodlStrategyTest::setup(); - let init_fn_args: Vec = (0,).into_val(&test.env); + let test = FixAprStrategyTest::setup(); + //MINT 100M to the strategy + let starting_amount = 100_000_000_000_0_000_000i128; + StellarAssetClient::new(&test.env, &test.token.address).mint(&test.strategy_admin, &starting_amount); + + let init_fn_args: Vec = vec![&test.env, + 1000u32.into_val(&test.env), + test.strategy_admin.into_val(&test.env), + starting_amount.into_val(&test.env), + ]; + test.strategy.initialize(&test.token.address, &init_fn_args); + let users = FixAprStrategyTest::generate_random_users(&test.env, 1); + let amount = -123456; - let result = test.strategy.try_deposit(&amount, &test.user); + let result = test.strategy.try_deposit(&amount, &users[0]); assert_eq!(result, Err(Ok(StrategyError::NegativeNotAllowed))); } -// check auth -#[test] -fn deposit_mock_auths() { - todo!() -} - +// test deposit with zero amount #[test] -fn deposit_and_withdrawal_flow() { - let test = HodlStrategyTest::setup(); - // let users = HodlStrategyTest::generate_random_users(&test.env, 1); - - // try deposit should return NotInitialized error before being initialize - - let result = test.strategy.try_deposit(&10_000_000, &test.user); - assert_eq!(result, Err(Ok(StrategyError::NotInitialized))); +fn deposit_with_zero_amount() { + let test = FixAprStrategyTest::setup(); + // MINT 100M to the strategy + let starting_amount = 100_000_000_000_0_000_000i128; + StellarAssetClient::new(&test.env, &test.token.address).mint(&test.strategy_admin, &starting_amount); + + let init_fn_args: Vec = vec![&test.env, + 1000u32.into_val(&test.env), + test.strategy_admin.into_val(&test.env), + starting_amount.into_val(&test.env), + ]; - // initialize - let init_fn_args: Vec = (0,).into_val(&test.env); - test.strategy.initialize(&test.token.address, &init_fn_args); + test.strategy.initialize(&test.token.address, &init_fn_args); - // Initial user token balance - let balance = test.token.balance(&test.user); + let users = FixAprStrategyTest::generate_random_users(&test.env, 1); - let amount = 123456; + let amount = 0; - // Deposit amount of token from the user to the strategy - test.strategy.deposit(&amount, &test.user); + test.strategy.deposit(&amount, &users[0]); +} - let balance_after_deposit = test.token.balance(&test.user); - assert_eq!(balance_after_deposit, balance - amount); +// test deposit with positive amount +#[test] +fn deposit() { + let test = FixAprStrategyTest::setup(); + // MINT 100M to the strategy + let starting_amount = 100_000_000_000_0_000_000i128; + StellarAssetClient::new(&test.env, &test.token.address).mint(&test.strategy_admin, &starting_amount); + + let init_fn_args: Vec = vec![&test.env, + 1000u32.into_val(&test.env), + test.strategy_admin.into_val(&test.env), + starting_amount.into_val(&test.env), + ]; - // Reading strategy balance - let strategy_balance_after_deposit = test.token.balance(&test.strategy.address); - assert_eq!(strategy_balance_after_deposit, amount); + test.strategy.initialize(&test.token.address, &init_fn_args); - // Reading user balance on strategy contract - let user_balance_on_strategy = test.strategy.balance(&test.user); - assert_eq!(user_balance_on_strategy, amount); + let users = FixAprStrategyTest::generate_random_users(&test.env, 1); + let amount = 1_000_0_00_000; + StellarAssetClient::new(&test.env, &test.token.address).mint(&users[0], &amount); - let amount_to_withdraw = 100_000; - // Withdrawing token from the strategy to user - test.strategy.withdraw(&amount_to_withdraw, &test.user); + test.strategy.deposit(&amount, &users[0]); + let user_balance = test.token.balance(&users[0]); + assert_eq!(user_balance, 0); +} - // Reading user balance in token - let balance = test.token.balance(&test.user); - assert_eq!(balance, balance_after_deposit + amount_to_withdraw); +// test deposit with amount exceeding balance +#[test] +#[should_panic(expected = "HostError: Error(Contract, #10)")] // Unauthorized +fn deposit_with_exceeding_balance() { + let test = FixAprStrategyTest::setup(); + // MINT 100M to the strategy + let starting_amount = 100_000_000_000_0_000_000i128; + StellarAssetClient::new(&test.env, &test.token.address).mint(&test.strategy_admin, &starting_amount); + + let init_fn_args: Vec = vec![&test.env, + 1000u32.into_val(&test.env), + test.strategy_admin.into_val(&test.env), + starting_amount.into_val(&test.env), + ]; - // Reading strategy balance in token - let balance = test.token.balance(&test.strategy.address); - assert_eq!(balance, amount - amount_to_withdraw); + test.strategy.initialize(&test.token.address, &init_fn_args); - // Reading user balance on strategy contract - let user_balance = test.strategy.balance(&test.user); - assert_eq!(user_balance, amount - amount_to_withdraw); + let users = FixAprStrategyTest::generate_random_users(&test.env, 1); - // now we will want to withdraw more of the remaining balance - let amount_to_withdraw = 200_000; - let result = test.strategy.try_withdraw(&amount_to_withdraw, &test.user); - assert_eq!(result, Err(Ok(StrategyError::InsufficientBalance))); + let amount = 1_000_0_00_000; + StellarAssetClient::new(&test.env, &test.token.address).mint(&users[0], &(&amount - 100_0_000_000)); -} \ No newline at end of file + test.strategy.deposit(&amount, &users[0]); +} diff --git a/apps/contracts/strategies/fixed_apr/src/test/events.rs b/apps/contracts/strategies/fixed_apr/src/test/events.rs deleted file mode 100644 index 239a9bd1..00000000 --- a/apps/contracts/strategies/fixed_apr/src/test/events.rs +++ /dev/null @@ -1,6 +0,0 @@ -// TODO: Write tests for events - -#[test] -fn test_events() { - todo!() -} \ No newline at end of file diff --git a/apps/contracts/strategies/fixed_apr/src/test/harvest.rs b/apps/contracts/strategies/fixed_apr/src/test/harvest.rs new file mode 100644 index 00000000..17c2711e --- /dev/null +++ b/apps/contracts/strategies/fixed_apr/src/test/harvest.rs @@ -0,0 +1,72 @@ +use soroban_sdk::{testutils::Ledger, token::StellarAssetClient, vec, IntoVal, Val, Vec}; + +use crate::{calculate_yield, test::FixAprStrategyTest}; + +#[test] +fn test_harvest_yields_multiple_users() { + let test = FixAprStrategyTest::setup(); + + //MINT 100M to the strategy + let starting_amount = 100_000_000_000_0_000_000i128; + StellarAssetClient::new(&test.env, &test.token.address).mint(&test.strategy_admin, &starting_amount); + + let apr = 1000u32; + let init_fn_args: Vec = vec![&test.env, + apr.clone().into_val(&test.env), + test.strategy_admin.into_val(&test.env), + starting_amount.into_val(&test.env), + ]; + + test.strategy.initialize(&test.token.address, &init_fn_args); + assert_eq!(test.token.balance(&test.strategy.address), starting_amount); + + let users = FixAprStrategyTest::generate_random_users(&test.env, 4); + + // Mint tokens to users + let user1_amount = 1_000_0_000_000i128; + StellarAssetClient::new(&test.env, &test.token.address).mint(&users[0], &user1_amount); + let user2_amount = 2_000_0_000_000i128; + StellarAssetClient::new(&test.env, &test.token.address).mint(&users[1], &user2_amount); + let user3_amount = 500_0_000_000i128; + StellarAssetClient::new(&test.env, &test.token.address).mint(&users[2], &user3_amount); + let user4_amount = 10_000_0_000_000i128; + StellarAssetClient::new(&test.env, &test.token.address).mint(&users[3], &user4_amount); + + // Deposit tokens for each user + test.strategy.deposit(&user1_amount, &users[0]); + let user1_balance = test.token.balance(&users[0]); + assert_eq!(user1_balance, 0); + + test.strategy.deposit(&user2_amount, &users[1]); + let user2_balance = test.token.balance(&users[1]); + assert_eq!(user2_balance, 0); + + test.strategy.deposit(&user3_amount, &users[2]); + let user3_balance = test.token.balance(&users[2]); + assert_eq!(user3_balance, 0); + + test.strategy.deposit(&user4_amount, &users[3]); + let user4_balance = test.token.balance(&users[3]); + assert_eq!(user4_balance, 0); + + // Simulate one year passing + let one_year_in_seconds = 31_536_000u64; + test.env.ledger().set_timestamp(test.env.ledger().timestamp() + one_year_in_seconds); + + // Harvest for each user + test.strategy.harvest(&users[0]); + test.strategy.harvest(&users[1]); + test.strategy.harvest(&users[2]); + test.strategy.harvest(&users[3]); + + // Check the harvested rewards for each user are correct + let user1_expected_reward = calculate_yield(user1_amount, apr, one_year_in_seconds); + let user2_expected_reward = calculate_yield(user2_amount, apr, one_year_in_seconds); + let user3_expected_reward = calculate_yield(user3_amount, apr, one_year_in_seconds); + let user4_expected_reward = calculate_yield(user4_amount, apr, one_year_in_seconds); + + assert_eq!(test.strategy.balance(&users[0]), user1_amount + user1_expected_reward); + assert_eq!(test.strategy.balance(&users[1]), user2_amount + user2_expected_reward); + assert_eq!(test.strategy.balance(&users[2]), user3_amount + user3_expected_reward); + assert_eq!(test.strategy.balance(&users[3]), user4_amount + user4_expected_reward); +} \ No newline at end of file diff --git a/apps/contracts/strategies/fixed_apr/src/test/initialize.rs b/apps/contracts/strategies/fixed_apr/src/test/initialize.rs index 41037473..0d085a72 100644 --- a/apps/contracts/strategies/fixed_apr/src/test/initialize.rs +++ b/apps/contracts/strategies/fixed_apr/src/test/initialize.rs @@ -1,21 +1,55 @@ // Cannot Initialize twice extern crate std; +use soroban_sdk::token::StellarAssetClient; +use soroban_sdk::vec; use soroban_sdk::{IntoVal, Vec, Val}; -use crate::test::HodlStrategyTest; +use crate::test::FixAprStrategyTest; use crate::test::StrategyError; +#[test] +fn initialize() { + let test = FixAprStrategyTest::setup(); + + //MINT 100M to the strategy + let starting_amount = 100_000_000_000_0_000_000i128; + StellarAssetClient::new(&test.env, &test.token.address).mint(&test.strategy_admin, &starting_amount); + + let init_fn_args: Vec = vec![&test.env, + 1000u32.into_val(&test.env), + test.strategy_admin.into_val(&test.env), + starting_amount.into_val(&test.env), + ]; + + test.strategy.initialize(&test.token.address, &init_fn_args); + + // get asset should return underlying asset + let underlying_asset = test.strategy.asset(); + assert_eq!(underlying_asset, test.token.address); + + // get contract token amount + let contract_token_amount = test.token.balance(&test.strategy.address); + assert_eq!(contract_token_amount, starting_amount); +} + #[test] fn cannot_initialize_twice() { - let test = HodlStrategyTest::setup(); + let test = FixAprStrategyTest::setup(); + + //MINT 100M to the strategy + let starting_amount = 100_000_000_000_0_000_000i128; + StellarAssetClient::new(&test.env, &test.token.address).mint(&test.strategy_admin, &starting_amount); - let init_fn_args: Vec = (0,).into_val(&test.env); + let init_fn_args: Vec = vec![&test.env, + 1000u32.into_val(&test.env), + test.strategy_admin.into_val(&test.env), + starting_amount.into_val(&test.env), + ]; test.strategy.initialize(&test.token.address, &init_fn_args); let result = test.strategy.try_initialize(&test.token.address , &init_fn_args); assert_eq!(result, Err(Ok(StrategyError::AlreadyInitialized))); // get asset should return underlying asset - let underlying_asset = test.strategy.asset(); assert_eq!(underlying_asset, test.token.address); } \ No newline at end of file diff --git a/apps/contracts/strategies/fixed_apr/src/test/withdraw.rs b/apps/contracts/strategies/fixed_apr/src/test/withdraw.rs index dd8aa9d5..b8f9592c 100644 --- a/apps/contracts/strategies/fixed_apr/src/test/withdraw.rs +++ b/apps/contracts/strategies/fixed_apr/src/test/withdraw.rs @@ -1,5 +1,143 @@ +use crate::{calculate_yield, test::FixAprStrategyTest}; +use defindex_strategy_core::StrategyError; +use soroban_sdk::testutils::Ledger; +use soroban_sdk::token::StellarAssetClient; +use soroban_sdk::vec; +use soroban_sdk::{IntoVal, Vec, Val}; #[test] fn withdraw() { - todo!() + let test = FixAprStrategyTest::setup(); + // MINT 100M to the strategy + let starting_amount = 100_000_000_000_0_000_000i128; + StellarAssetClient::new(&test.env, &test.token.address).mint(&test.strategy_admin, &starting_amount); + + let init_fn_args: Vec = vec![&test.env, + 1000u32.into_val(&test.env), + test.strategy_admin.into_val(&test.env), + starting_amount.into_val(&test.env), + ]; + + test.strategy.initialize(&test.token.address, &init_fn_args); + + let users = FixAprStrategyTest::generate_random_users(&test.env, 1); + + let amount = 1_000_0_00_000; + StellarAssetClient::new(&test.env, &test.token.address).mint(&users[0], &amount); + + test.strategy.deposit(&amount, &users[0]); + let user_balance = test.token.balance(&users[0]); + assert_eq!(user_balance, 0); + + test.strategy.withdraw(&amount, &users[0]); + let user_balance_after_withdraw = test.token.balance(&users[0]); + assert_eq!(user_balance_after_withdraw, amount); +} + +#[test] +fn withdraw_with_harvest() { + let test = FixAprStrategyTest::setup(); + // MINT 100M to the strategy + let starting_amount = 100_000_000_000_0_000_000i128; + StellarAssetClient::new(&test.env, &test.token.address).mint(&test.strategy_admin, &starting_amount); + + let init_fn_args: Vec = vec![&test.env, + 1000u32.into_val(&test.env), + test.strategy_admin.into_val(&test.env), + starting_amount.into_val(&test.env), + ]; + + test.strategy.initialize(&test.token.address, &init_fn_args); + + let users = FixAprStrategyTest::generate_random_users(&test.env, 1); + + let amount = 1_000_0_00_000; + StellarAssetClient::new(&test.env, &test.token.address).mint(&users[0], &amount); + + test.strategy.deposit(&amount, &users[0]); + let user_balance = test.token.balance(&users[0]); + assert_eq!(user_balance, 0); + + // Simulate one year passing + let one_year_in_seconds = 31_536_000u64; + test.env.ledger().set_timestamp(test.env.ledger().timestamp() + one_year_in_seconds); + + test.strategy.harvest(&users[0]); + + let expected_reward = calculate_yield(amount, 1000u32, one_year_in_seconds); + let user_balance_after_harvest = test.strategy.balance(&users[0]); + assert_eq!(user_balance_after_harvest, amount + expected_reward); + + test.strategy.withdraw(&amount, &users[0]); + let user_balance_after_withdraw = test.token.balance(&users[0]); + assert_eq!(user_balance_after_withdraw, amount); +} + +#[test] +fn withdraw_then_harvest_then_withdraw_again() { + let test = FixAprStrategyTest::setup(); + // MINT 100M to the strategy + let starting_amount = 100_000_000_000_0_000_000i128; + StellarAssetClient::new(&test.env, &test.token.address).mint(&test.strategy_admin, &starting_amount); + + let init_fn_args: Vec = vec![&test.env, + 1000u32.into_val(&test.env), + test.strategy_admin.into_val(&test.env), + starting_amount.into_val(&test.env), + ]; + + test.strategy.initialize(&test.token.address, &init_fn_args); + + let users = FixAprStrategyTest::generate_random_users(&test.env, 1); + + let amount = 1_000_0_000_000; + StellarAssetClient::new(&test.env, &test.token.address).mint(&users[0], &amount); + + test.strategy.deposit(&amount, &users[0]); + let user_balance = test.token.balance(&users[0]); + assert_eq!(user_balance, 0); + + // Simulate one year passing + let one_year_in_seconds = 31_536_000u64; + test.env.ledger().set_timestamp(test.env.ledger().timestamp() + one_year_in_seconds); + + let user_balance_before_harvest = test.strategy.balance(&users[0]); + assert_eq!(user_balance_before_harvest, amount); + + test.strategy.withdraw(&amount, &users[0]); + let user_balance_after_withdraw = test.token.balance(&users[0]); + assert_eq!(user_balance_after_withdraw, amount); + + // test.strategy.harvest(&users[0]); + + // let expected_reward = calculate_yield(amount, 1000u32, one_year_in_seconds); + // let user_balance_after_harvest = test.strategy.balance(&users[0]); + // assert_eq!(user_balance_after_harvest, expected_reward); + + // test.strategy.withdraw(&expected_reward, &users[0]); + // let user_balance_after_second_withdraw = test.token.balance(&users[0]); + // assert_eq!(user_balance_after_second_withdraw, amount + expected_reward); +} + +#[test] +fn withdraw_with_no_balance() { + let test = FixAprStrategyTest::setup(); + // MINT 100M to the strategy + let starting_amount = 100_000_000_000_0_000_000i128; + StellarAssetClient::new(&test.env, &test.token.address).mint(&test.strategy_admin, &starting_amount); + + let init_fn_args: Vec = vec![&test.env, + 1000u32.into_val(&test.env), + test.strategy_admin.into_val(&test.env), + starting_amount.into_val(&test.env), + ]; + + test.strategy.initialize(&test.token.address, &init_fn_args); + + let users = FixAprStrategyTest::generate_random_users(&test.env, 1); + + let amount = 1_000_0_00_000; + + let result = test.strategy.try_withdraw(&amount, &users[0]); + assert_eq!(result, Err(Ok(StrategyError::InsufficientBalance))); } \ No newline at end of file diff --git a/apps/contracts/strategies/fixed_apr/src/yield_balance.rs b/apps/contracts/strategies/fixed_apr/src/yield_balance.rs new file mode 100644 index 00000000..bf60f929 --- /dev/null +++ b/apps/contracts/strategies/fixed_apr/src/yield_balance.rs @@ -0,0 +1,43 @@ +use soroban_sdk::{Address, Env}; + +use crate::storage::{DataKey, INSTANCE_BUMP_AMOUNT, INSTANCE_LIFETIME_THRESHOLD}; +use crate::StrategyError; + +pub fn read_yield(e: &Env, addr: Address) -> i128 { + let key = DataKey::YieldBalance(addr); + if let Some(balance) = e.storage().persistent().get::(&key) { + e.storage() + .persistent() + .extend_ttl(&key, INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT); + balance + } else { + 0 + } +} + +fn write_yield(e: &Env, addr: Address, amount: i128) { + let key = DataKey::YieldBalance(addr); + e.storage().persistent().set(&key, &amount); + e.storage() + .persistent() + .extend_ttl(&key, INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT); +} + +pub fn receive_yield(e: &Env, addr: Address, amount: i128) { + let balance = read_yield(e, addr.clone()); + + let new_balance = balance.checked_add(amount) + .expect("Integer overflow occurred while adding balance."); + + write_yield(e, addr, new_balance); +} + +pub fn spend_yield(e: &Env, addr: Address, amount: i128) -> Result<(), StrategyError> { + + let balance = read_yield(e, addr.clone()); + if balance < amount { + return Err(StrategyError::InsufficientBalance); + } + write_yield(e, addr, balance - amount); + Ok(()) +} From da6efd388b837ed4e12033591d3fe09db33b3e50 Mon Sep 17 00:00:00 2001 From: esteblock Date: Mon, 11 Nov 2024 10:30:47 -0300 Subject: [PATCH 3/4] fix warnings xycloans@ --- apps/contracts/strategies/soroswap/src/lib.rs | 8 ++++---- apps/contracts/strategies/xycloans/src/lib.rs | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/contracts/strategies/soroswap/src/lib.rs b/apps/contracts/strategies/soroswap/src/lib.rs index 3d4bf827..77daf1b9 100644 --- a/apps/contracts/strategies/soroswap/src/lib.rs +++ b/apps/contracts/strategies/soroswap/src/lib.rs @@ -123,7 +123,7 @@ impl DeFindexStrategyTrait for SoroswapAdapter { let total_swapped_amount = swap_result.last().unwrap(); // Add liquidity - let result = soroswap_router_client.add_liquidity( + let _result = soroswap_router_client.add_liquidity( &usdc_address, &xlm_address, &swap_amount, @@ -137,7 +137,7 @@ impl DeFindexStrategyTrait for SoroswapAdapter { Ok(()) } - fn harvest(e: Env, from: Address) -> Result<(), StrategyError> { + fn harvest(e: Env, _from: Address) -> Result<(), StrategyError> { check_initialized(&e)?; extend_instance_ttl(&e); @@ -146,7 +146,7 @@ impl DeFindexStrategyTrait for SoroswapAdapter { fn withdraw( e: Env, - amount: i128, + _amount: i128, from: Address, ) -> Result { from.require_auth(); @@ -186,7 +186,7 @@ impl DeFindexStrategyTrait for SoroswapAdapter { ]); // Remove liquidity - let (usdc_amount, xlm_amount) = soroswap_router_client.remove_liquidity( + let (_usdc_amount, xlm_amount) = soroswap_router_client.remove_liquidity( &usdc_address, &xlm_address, &lp_balance, diff --git a/apps/contracts/strategies/xycloans/src/lib.rs b/apps/contracts/strategies/xycloans/src/lib.rs index c47d5732..8383087f 100644 --- a/apps/contracts/strategies/xycloans/src/lib.rs +++ b/apps/contracts/strategies/xycloans/src/lib.rs @@ -5,11 +5,11 @@ mod storage; mod soroswap_router; mod xycloans_pool; -use soroban_sdk::{auth::{ContractContext, InvokerContractAuthEntry, SubContractInvocation}, contract, contractimpl, vec, Address, Env, IntoVal, Symbol, Val, Vec}; +use soroban_sdk::{contract, contractimpl, Address, Env, IntoVal, Val, Vec}; use storage::{ - extend_instance_ttl, get_soroswap_router_address, get_pool_token, get_token_in, get_xycloans_pool_address, is_initialized, set_initialized, set_soroswap_router_address, set_pool_token, set_token_in, set_xycloans_pool_address, set_soroswap_factory_address, get_soroswap_factory_address + extend_instance_ttl, get_pool_token, get_token_in, get_xycloans_pool_address, is_initialized, set_initialized, set_soroswap_router_address, set_pool_token, set_token_in, set_xycloans_pool_address, set_soroswap_factory_address, get_soroswap_factory_address }; -use soroswap_router::{get_amount_out, get_reserves, pair_for, swap, SoroswapRouterClient}; +use soroswap_router::{get_amount_out, get_reserves, swap}; use xycloans_pool::XycloansPoolClient; use defindex_strategy_core::{StrategyError, DeFindexStrategyTrait}; @@ -92,7 +92,7 @@ impl DeFindexStrategyTrait for XycloansAdapter { Ok(()) } - fn harvest(e: Env, from: Address) -> Result<(), StrategyError> { + fn harvest(e: Env, _from: Address) -> Result<(), StrategyError> { check_initialized(&e)?; extend_instance_ttl(&e); @@ -101,7 +101,7 @@ impl DeFindexStrategyTrait for XycloansAdapter { fn withdraw( e: Env, - amount: i128, + _amount: i128, from: Address, ) -> Result { from.require_auth(); From 7eefd81dfe577fcc2a23fa6eea69009b51b5d267 Mon Sep 17 00:00:00 2001 From: esteblock Date: Mon, 11 Nov 2024 10:37:39 -0300 Subject: [PATCH 4/4] clean warnings vault and factory --- apps/contracts/factory/src/test.rs | 2 +- apps/contracts/factory/src/test/all_flow.rs | 2 +- apps/contracts/factory/src/test/initialize.rs | 2 +- apps/contracts/vault/src/lib.rs | 2 +- apps/contracts/vault/src/test/deposit.rs | 4 +- apps/contracts/vault/src/test/initialize.rs | 2 +- apps/contracts/vault/src/token/mod.rs | 2 +- apps/contracts/vault/src/utils.rs | 68 +++++++++---------- 8 files changed, 42 insertions(+), 42 deletions(-) diff --git a/apps/contracts/factory/src/test.rs b/apps/contracts/factory/src/test.rs index dc9e065d..0a3d4368 100644 --- a/apps/contracts/factory/src/test.rs +++ b/apps/contracts/factory/src/test.rs @@ -43,7 +43,7 @@ mod defindex_vault_contract { // Create Test Token pub(crate) fn create_token_contract<'a>(e: &Env, admin: &Address) -> SorobanTokenClient<'a> { - SorobanTokenClient::new(e, &e.register_stellar_asset_contract(admin.clone())) + SorobanTokenClient::new(e, &e.register_stellar_asset_contract_v2(admin.clone()).address()) } pub(crate) fn get_token_admin_client<'a>( diff --git a/apps/contracts/factory/src/test/all_flow.rs b/apps/contracts/factory/src/test/all_flow.rs index fa0e5e9b..52405f9d 100644 --- a/apps/contracts/factory/src/test/all_flow.rs +++ b/apps/contracts/factory/src/test/all_flow.rs @@ -1,4 +1,4 @@ -use soroban_sdk::{vec, BytesN, Map, String, Vec}; +use soroban_sdk::{vec, BytesN, String}; use crate::test::{create_asset_params, defindex_vault_contract::{self, Investment}, DeFindexFactoryTest}; diff --git a/apps/contracts/factory/src/test/initialize.rs b/apps/contracts/factory/src/test/initialize.rs index 7455baff..64cd8abc 100644 --- a/apps/contracts/factory/src/test/initialize.rs +++ b/apps/contracts/factory/src/test/initialize.rs @@ -1,4 +1,4 @@ -use soroban_sdk::{vec, Address, BytesN, String, Vec}; +use soroban_sdk::{BytesN, String}; use crate::error::FactoryError; use crate::test::{create_asset_params, DeFindexFactoryTest}; diff --git a/apps/contracts/vault/src/lib.rs b/apps/contracts/vault/src/lib.rs index 69a52ada..6f45c7c1 100755 --- a/apps/contracts/vault/src/lib.rs +++ b/apps/contracts/vault/src/lib.rs @@ -33,7 +33,7 @@ use models::{ OptionalSwapDetailsExactOut, }; use storage::{ - get_assets, set_asset, set_defindex_protocol_fee_receiver, set_factory, set_last_fee_assesment, + get_assets, set_asset, set_defindex_protocol_fee_receiver, set_factory, set_total_assets, set_vault_fee, extend_instance_ttl }; use strategies::{ diff --git a/apps/contracts/vault/src/test/deposit.rs b/apps/contracts/vault/src/test/deposit.rs index 0da6e6a0..35626a23 100644 --- a/apps/contracts/vault/src/test/deposit.rs +++ b/apps/contracts/vault/src/test/deposit.rs @@ -71,7 +71,7 @@ fn deposit_amounts_desired_more_length() { let test = DeFindexVaultTest::setup(); test.env.mock_all_auths(); let strategy_params_token0 = create_strategy_params_token0(&test); - let strategy_params_token1 = create_strategy_params_token1(&test); + // let strategy_params_token1 = create_strategy_params_token1(&test); // initialize with 2 assets let assets: Vec = sorobanvec![ @@ -669,7 +669,7 @@ fn deposit_several_assets_min_greater_than_optimal() { assert_eq!(deposit_result, Err(Ok(ContractError::InsufficientAmount))); // now we manage to deposit - let deposit_result=test.defindex_contract.deposit( + test.defindex_contract.deposit( &sorobanvec![&test.env, amount0, amount1], &sorobanvec![&test.env, amount0, amount1], &users[0], diff --git a/apps/contracts/vault/src/test/initialize.rs b/apps/contracts/vault/src/test/initialize.rs index 4c5faf24..9c9e5fa0 100644 --- a/apps/contracts/vault/src/test/initialize.rs +++ b/apps/contracts/vault/src/test/initialize.rs @@ -86,7 +86,7 @@ fn test_initialize_with_unsupported_strategy() { #[test] fn test_initialize_with_empty_asset_allocation() { let test = DeFindexVaultTest::setup(); - let strategy_params_token0 = create_strategy_params_token0(&test); + // let strategy_params_token0 = create_strategy_params_token0(&test); let assets: Vec = sorobanvec![&test.env]; diff --git a/apps/contracts/vault/src/token/mod.rs b/apps/contracts/vault/src/token/mod.rs index 77d0ace3..869bda67 100644 --- a/apps/contracts/vault/src/token/mod.rs +++ b/apps/contracts/vault/src/token/mod.rs @@ -8,6 +8,6 @@ mod storage_types; mod total_supply; pub use contract::VaultToken; -pub use contract::VaultTokenClient; +// pub use contract::VaultTokenClient; pub use contract::{internal_burn, internal_mint}; pub use metadata::write_metadata; diff --git a/apps/contracts/vault/src/utils.rs b/apps/contracts/vault/src/utils.rs index d8f9bad5..fd893167 100644 --- a/apps/contracts/vault/src/utils.rs +++ b/apps/contracts/vault/src/utils.rs @@ -83,40 +83,40 @@ pub fn calculate_asset_amounts_for_dftokens( asset_amounts } -pub fn calculate_dftokens_from_asset_amounts( - env: &Env, - asset_amounts: Map, // The input asset amounts - total_managed_funds: Map, // The total managed funds for each asset -) -> Result { - let total_supply = VaultToken::total_supply(env.clone()); // Total dfToken supply - - // Initialize the minimum dfTokens corresponding to each asset - let mut min_df_tokens: Option = None; - - // Iterate over each asset in the input map - for (asset_address, input_amount) in asset_amounts.iter() { - // Get the total managed amount for this asset - let managed_amount = total_managed_funds.get(asset_address.clone()).unwrap_or(0); - - // Ensure the managed amount is not zero to prevent division by zero - if managed_amount == 0 { - return Err(ContractError::InsufficientManagedFunds); - } - - // Calculate the dfTokens corresponding to this asset's amount - let df_tokens_for_asset = (input_amount * total_supply) / managed_amount; - - // If this is the first asset or if the calculated df_tokens_for_asset is smaller, update the minimum df_tokens - if let Some(current_min_df_tokens) = min_df_tokens { - min_df_tokens = Some(current_min_df_tokens.min(df_tokens_for_asset)); - } else { - min_df_tokens = Some(df_tokens_for_asset); - } - } - - // Return the minimum dfTokens across all assets - min_df_tokens.ok_or(ContractError::NoAssetsProvided) -} +// pub fn calculate_dftokens_from_asset_amounts( +// env: &Env, +// asset_amounts: Map, // The input asset amounts +// total_managed_funds: Map, // The total managed funds for each asset +// ) -> Result { +// let total_supply = VaultToken::total_supply(env.clone()); // Total dfToken supply + +// // Initialize the minimum dfTokens corresponding to each asset +// let mut min_df_tokens: Option = None; + +// // Iterate over each asset in the input map +// for (asset_address, input_amount) in asset_amounts.iter() { +// // Get the total managed amount for this asset +// let managed_amount = total_managed_funds.get(asset_address.clone()).unwrap_or(0); + +// // Ensure the managed amount is not zero to prevent division by zero +// if managed_amount == 0 { +// return Err(ContractError::InsufficientManagedFunds); +// } + +// // Calculate the dfTokens corresponding to this asset's amount +// let df_tokens_for_asset = (input_amount * total_supply) / managed_amount; + +// // If this is the first asset or if the calculated df_tokens_for_asset is smaller, update the minimum df_tokens +// if let Some(current_min_df_tokens) = min_df_tokens { +// min_df_tokens = Some(current_min_df_tokens.min(df_tokens_for_asset)); +// } else { +// min_df_tokens = Some(df_tokens_for_asset); +// } +// } + +// // Return the minimum dfTokens across all assets +// min_df_tokens.ok_or(ContractError::NoAssetsProvided) +// } pub fn calculate_optimal_amounts_and_shares_with_enforced_asset( e: &Env,