Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: time handling for interest #17

Merged
merged 4 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,6 @@ packages/*
# generated contract client imports
src/contracts/*
!src/contracts/util.ts

# contract unit test test-snapshots
**/test_snapshots
28 changes: 20 additions & 8 deletions contracts/loan_manager/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::positions;
use crate::storage_types::{POSITIONS_BUMP_AMOUNT, POSITIONS_LIFETIME_THRESHOLD};
use crate::storage_types::{LoansDataKey, POSITIONS_BUMP_AMOUNT, POSITIONS_LIFETIME_THRESHOLD};

use soroban_sdk::{
contract, contractimpl, vec, Address, Env, IntoVal, Map, Symbol, TryFromVal, Val, Vec,
Expand Down Expand Up @@ -81,13 +81,25 @@ impl LoansTrait for LoansContract {
}

fn add_interest(e: Env) {
// Get current interest rates of pools.

/*
We calculate interest for ledgers_between from a given APY approximation simply by dividing the rate r with ledgers in a year
and multiplying it with ledgers_between. This would result in slightly different total yearly interest, e.g. 12% -> 12.7% total.
Perfect calculations are impossible in real world time as we must use ledgers as our time and ledger times vary between 5-6s.
*/
// TODO: we must store the init ledger for loans as loans started on different times would pay the same amount of interest on the given time.

let current_ledger = e.ledger().sequence();

let key: LoansDataKey = LoansDataKey::LastUpdated;
let previous_ledger_val: Val = e
.storage()
.persistent()
.get(&key)
.unwrap_or(current_ledger.into_val(&e)); // If there is no previous ledger, use current.
Copy link
Contributor

Choose a reason for hiding this comment

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

Googlasin mitä eroa on unwrap_or ja unwrap_or_else välillä koska en tiennyt. Ei tässä kohtaa väliä kumpaa käyttää, mutta jos toi else-haara olisi kallista laskea, niin unwrap_or_else olisi helppo tapa optimoida.

https://stackoverflow.com/a/56726618

let previous_ledger: u32 =
u32::try_from_val(&e, &previous_ledger_val).expect("Failed to convert Val to u32");

let _ledgers_since_update: u32 = current_ledger - previous_ledger; // Currently unused but is a placeholder for interest calculations. Now time is handled.

// Update current ledger as the new 'last time'

Expand All @@ -100,23 +112,21 @@ impl LoansTrait for LoansContract {
.get(&Symbol::new(&e, "Addresses"))
.unwrap();
for user in addresses.iter() {
// Construct the key for each user's loan
let key = (Symbol::new(&e, "Loan"), user.clone());

// get the loan from storage as a Val
let loan: Val = e
.storage()
.persistent()
.get::<(Symbol, Address), Val>(&key)
.unwrap();
// Convert the Val to Map<Symbol, Val>

let mut loan_map: Map<Symbol, Val> =
Map::try_from_val(&e, &loan).expect("Failed to convert Val to Map");
// Get the value of "borrowed" as Val, then convert it to i128

let borrowed: Val = loan_map.get(Symbol::new(&e, "borrowed")).unwrap();
let borrowed_as_int: i128 =
i128::try_from_val(&e, &borrowed).expect("Failed to convert Val to i128");
// Calculate new borrowed

// FIXME: the calculation doesn't work, perhaps because of the change in types. OR it could be that the value is not retrieved properly
let interest_rate: i128 = 12000;
let interest_amount: i128 = borrowed_as_int * interest_rate;
Expand All @@ -134,5 +144,7 @@ impl LoansTrait for LoansContract {
);
// TODO: this should also invoke the pools and update the amounts lended to liabilities.
}

e.storage().persistent().set(&key, &current_ledger);
}
}
1 change: 1 addition & 0 deletions contracts/loan_manager/src/storage_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ pub enum LoansDataKey {
// Users positions in the pool
Loan(Address),
Addresses,
LastUpdated,
}
128 changes: 122 additions & 6 deletions contracts/loan_pool/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub trait LoanPoolTrait {
fn initialize(e: Env, token: Address);

// Deposits token. Also, mints pool shares for the "user" Identifier.
fn deposit(e: Env, user: Address, amount: i128);
fn deposit(e: Env, user: Address, amount: i128) -> i128;

// Transfers share tokens back, burns them and gives corresponding amount of tokens back to user. Returns amount of tokens withdrawn
fn withdraw(e: Env, user: Address, share_amount: i128) -> (i128, i128);
Expand Down Expand Up @@ -46,7 +46,7 @@ impl LoanPoolTrait for LoanPoolContract {
pool::write_available_balance(&e, 0);
}

fn deposit(e: Env, user: Address, amount: i128) {
fn deposit(e: Env, user: Address, amount: i128) -> i128 {
user.require_auth(); // Depositor needs to authorize the deposit
assert!(amount > 0, "Amount must be positive!");

Expand All @@ -56,16 +56,19 @@ impl LoanPoolTrait for LoanPoolContract {
let client = token::Client::new(&e, &pool::read_token(&e));
client.transfer(&user, &e.current_contract_address(), &amount);

// TODO: Increase AvailableBalance
// TODO: Increase TotalShares
// TODO: Increase TotalBalance
// TODO: these need to be replaced with increase rather than write so that it wont overwrite the values.
pool::write_available_balance(&e, amount);
pool::write_total_shares(&e, amount);
pool::write_total_balance(&e, amount);

// Increase users position in pool as they deposit
// as this is deposit amount is added to receivables and
// liabilities & collateral stays intact
let liabilities: i128 = 0; // temp test param
let collateral: i128 = 0; // temp test param
positions::increase_positions(&e, user.clone(), amount, liabilities, collateral);

amount
}

fn withdraw(e: Env, user: Address, amount: i128) -> (i128, i128) {
Expand Down Expand Up @@ -109,7 +112,7 @@ impl LoanPoolTrait for LoanPoolContract {
*/
let address = String::from_str(
&e,
"CCR7ARWZN4WODMEWVTRCMPPJJQKE2MBKUPJBSYWCDEOT3OLBPAPEGLPH",
"CB6MHNR6FJMQHJZDWOKAU4KESR4OARLPZ4RMN57R55P2QUBH4QJENHLY",
Copy link
Contributor

Choose a reason for hiding this comment

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

Tää oli kaiketi väliaikanen ratkasu?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Joo tämä juuri pitäisi korjata 'Store loan manager’s ID for liquidity pool' tiketissä.

);
let contract: Address = Address::from_string(&address);
contract.require_auth();
Expand Down Expand Up @@ -164,3 +167,116 @@ impl LoanPoolTrait for LoanPoolContract {
pool::read_total_balance(&e)
}
}

#[cfg(test)]
mod tests {
use super::*; // This imports LoanPoolContract and everything else from the parent module
use soroban_sdk::{
testutils::Address as _,
token::{Client as TokenClient, StellarAssetClient},
vec, Env, IntoVal,
};

#[test]
fn good_deposit() {
let e: Env = Env::default();
e.mock_all_auths();

let admin: Address = Address::generate(&e);
let token_contract_id = e.register_stellar_asset_contract(admin.clone());
let stellar_asset = StellarAssetClient::new(&e, &token_contract_id);
let token = TokenClient::new(&e, &token_contract_id);

let user = Address::generate(&e);
stellar_asset.mint(&user, &1000);
assert_eq!(token.balance(&user), 1000);

let contract_id = e.register_contract(None, LoanPoolContract);
let amount_i: i128 = 100;
let amount: Val = amount_i.into_val(&e);

let args: soroban_sdk::Vec<Val> = vec![&e, user.to_val(), amount];
let init_args: soroban_sdk::Vec<Val> = vec![&e, token_contract_id.to_val()];

let _init_result: () =
e.invoke_contract(&contract_id, &Symbol::new(&e, "initialize"), init_args);

let result: i128 = e.invoke_contract(&contract_id, &Symbol::new(&e, "deposit"), args);

assert_eq!(result, amount_i);

// Add assertions to validate expected behavior
}

#[test]
fn good_withdraw() {
let e: Env = Env::default();
e.mock_all_auths();

let admin: Address = Address::generate(&e);
let token_contract_id = e.register_stellar_asset_contract(admin.clone());
let stellar_asset = StellarAssetClient::new(&e, &token_contract_id);
let token = TokenClient::new(&e, &token_contract_id);

let user = Address::generate(&e);
stellar_asset.mint(&user, &1000);
assert_eq!(token.balance(&user), 1000);

let contract_id = e.register_contract(None, LoanPoolContract);
let amount_i: i128 = 100;
let amount: Val = amount_i.into_val(&e);

let args: soroban_sdk::Vec<Val> = vec![&e, user.to_val(), amount];
let init_args: soroban_sdk::Vec<Val> = vec![&e, token_contract_id.to_val()];

let _init_result: () =
e.invoke_contract(&contract_id, &Symbol::new(&e, "initialize"), init_args);

let result: i128 = e.invoke_contract(&contract_id, &Symbol::new(&e, "deposit"), args);

assert_eq!(result, amount_i);

let withdraw_args = vec![&e, user.to_val(), amount];
let withdraw_result: (i128, i128) =
e.invoke_contract(&contract_id, &Symbol::new(&e, "withdraw"), withdraw_args);

assert_eq!(withdraw_result, (amount_i, amount_i));
}

#[test]
#[should_panic(expected = "Amount can not be greater than receivables!")]
fn withdraw_more_than_balance() {
let e: Env = Env::default();
e.mock_all_auths();

let admin: Address = Address::generate(&e);
let token_contract_id = e.register_stellar_asset_contract(admin.clone());
let stellar_asset = StellarAssetClient::new(&e, &token_contract_id);
let token = TokenClient::new(&e, &token_contract_id);

let user = Address::generate(&e);
stellar_asset.mint(&user, &1000);
assert_eq!(token.balance(&user), 1000);

let contract_id = e.register_contract(None, LoanPoolContract);
let amount_i: i128 = 100;
let amount: Val = amount_i.into_val(&e);

let args: soroban_sdk::Vec<Val> = vec![&e, user.to_val(), amount];
let init_args: soroban_sdk::Vec<Val> = vec![&e, token_contract_id.to_val()];

let _init_result: () =
e.invoke_contract(&contract_id, &Symbol::new(&e, "initialize"), init_args);

let result: i128 = e.invoke_contract(&contract_id, &Symbol::new(&e, "deposit"), args);

assert_eq!(result, amount_i);

let amount_i_2: i128 = 200;
let amount_2: Val = amount_i_2.into_val(&e);

let withdraw_args = vec![&e, user.to_val(), amount_2];
let _withdraw_result: (i128, i128) =
e.invoke_contract(&contract_id, &Symbol::new(&e, "withdraw"), withdraw_args);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Solidit testit 👏

}
Loading
Loading