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: health factor and reflector price oracle #19

Merged
merged 12 commits into from
Aug 26, 2024
24 changes: 0 additions & 24 deletions .github/workflows/clippy.yml

This file was deleted.

21 changes: 19 additions & 2 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,29 @@ env:
CARGO_TERM_COLOR: always

jobs:
build:
build-rust:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Install Rust
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable

- name: Build Reflector
run: |
cd contracts/reflector
cargo build --release --target wasm32-unknown-unknown
cd ../..

- name: Build
run: cargo build --verbose
run: cargo build --release --target wasm32-unknown-unknown

- name: Run tests
run: cargo test --verbose

- name: Run clippy
run: cargo clippy -- -D warnings
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[build]
target = "wasm32-unknown-unknown"

[workspace]
resolver = "2"
members = ["contracts/*"]
Expand Down
172 changes: 166 additions & 6 deletions contracts/loan_manager/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use crate::oracle::{self, Asset};
use crate::positions;
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,
contract, contractimpl, vec, Address, Env, IntoVal, Map, String, Symbol, TryFromVal, Val, Vec,
};

#[allow(dead_code)]
pub trait LoansTrait {
// Initialize new loan
fn initialize(
e: Env,
user: Address,
Expand All @@ -16,8 +16,15 @@ pub trait LoansTrait {
collateral: i128,
collateral_from: Address,
);
// Add accumulated interest to the borrowed capital
fn add_interest(e: Env);
fn calculate_health_factor(
e: &Env,
reflector_contract_id: Address,
token_ticker: Symbol,
token_amount: i128,
token_collateral_ticker: Symbol,
token_collateral_amount: i128,
) -> i128;
}

#[allow(dead_code)]
Expand All @@ -34,11 +41,30 @@ impl LoansTrait for LoansContract {
collateral: i128,
collateral_from: Address,
) {
// Require user authorization
user.require_auth();

// First thing should be to check if the collateral is enough => health factor large enough
// Create args for collateral deposit
let reflector_id: String = String::from_str(
&e,
"CBKZFI26PDCZUJ5HYYKVB5BWCNYUSNA5LVL4R2JTRVSOB4XEP7Y34OPN",
);
let token_ticker: Symbol = Symbol::new(&e, "USDC"); // temporary
let collateral_ticker: Symbol = Symbol::new(&e, "XLM"); // temporary
let health_factor: i128 = Self::calculate_health_factor(
&e,
Address::from_string(&reflector_id),
token_ticker,
borrowed,
collateral_ticker,
collateral,
);

// Health factor has to be over 1.2 for the loan to be initialized.
// Health factor is defined as so: 1.0 = 10000000_i128
assert!(
health_factor > 12000000,
Teolhyn marked this conversation as resolved.
Show resolved Hide resolved
"Health factor must be over 1.2 to create a new loan!"
Teolhyn marked this conversation as resolved.
Show resolved Hide resolved
);

let user_val: Val = Val::try_from_val(&e, &user).unwrap();
let collateral_val: Val = Val::try_from_val(&e, &collateral).unwrap();
let args: soroban_sdk::Vec<Val> = vec![&e, user_val, collateral_val];
Expand All @@ -63,6 +89,7 @@ impl LoansTrait for LoansContract {
borrowed_from,
deposited_collateral,
collateral_from,
health_factor,
);

// Update the list of addresses with loans
Expand Down Expand Up @@ -147,4 +174,137 @@ impl LoansTrait for LoansContract {

e.storage().persistent().set(&key, &current_ledger);
}

fn calculate_health_factor(
e: &Env,
reflector_contract_id: Address,
token_ticker: Symbol,
token_amount: i128,
token_collateral_ticker: Symbol,
token_collateral_amount: i128,
) -> i128 {
let reflector_contract: oracle::Client = oracle::Client::new(e, &reflector_contract_id);

// get the price and calculate the value of the collateral
let collateral_asset: Asset = Asset::Other(token_collateral_ticker);

let collateral_asset_price: oracle::PriceData =
reflector_contract.lastprice(&collateral_asset).unwrap();
let collateral_value: i128 = collateral_asset_price.price * token_collateral_amount;

// get the price and calculate the value of the borrowed asset
let borrowed_asset: Asset = Asset::Other(token_ticker);
let asset_price: oracle::PriceData = reflector_contract.lastprice(&borrowed_asset).unwrap();
let borrowed_value: i128 = asset_price.price * token_amount;

let health_ratio: i128 = collateral_value * 10000000_i128 / borrowed_value;
Teolhyn marked this conversation as resolved.
Show resolved Hide resolved

health_ratio
}
}
#[cfg(test)]
#[allow(dead_code, unused_imports)]
mod tests {
use super::*;
use soroban_sdk::{
testutils::Address as _,
token::{Client as TokenClient, StellarAssetClient},
Env, TryIntoVal,
};

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PriceData {
// The price in contracts' base asset and decimals.
pub price: i128,
// The timestamp of the price.
pub timestamp: u64,
}

impl TryFromVal<Env, Val> for PriceData {
type Error = soroban_sdk::Error;

fn try_from_val(env: &Env, val: &Val) -> Result<Self, Self::Error> {
// Assuming `val` is a tuple with two elements: (price, timestamp)
let tuple: (i128, u64) = val.try_into_val(env)?;
Ok(PriceData {
price: tuple.0,
timestamp: tuple.1,
})
}
}

impl TryIntoVal<Env, Val> for PriceData {
type Error = soroban_sdk::Error;

fn try_into_val(&self, env: &Env) -> Result<Val, Self::Error> {
// Convert `PriceData` into a tuple and then into `Val`
let tuple: (i128, u64) = (self.price, self.timestamp);
tuple.try_into_val(env)
}
}

#[contract]
pub struct MockBorrowContract;

#[contractimpl]
impl MockBorrowContract {
pub fn borrow(_e: Env, _user: Address, _amount: i128) {}
}

#[contract]
pub struct MockCollateralContract;

#[contractimpl]
impl MockCollateralContract {
pub fn deposit_collateral(_e: Env, _user: Address, _collateral_amount: i128) {}
}

#[contract]
pub struct MockValueContract;

#[contractimpl]
impl MockValueContract {
pub fn lastprice(ticker: Asset) -> Option<PriceData> {
let _ticker_lended = ticker;
let price_data = PriceData {
price: 1,
timestamp: 1,
};
Some(price_data)
}
}

#[test]
fn create_loan() {
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 admin2: Address = Address::generate(&e);
let token_contract_id2 = e.register_stellar_asset_contract(admin2.clone());
let stellar_asset2 = StellarAssetClient::new(&e, &token_contract_id2);
let collateral_token = TokenClient::new(&e, &token_contract_id);

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

let contract_id: Address = e.register_contract(None, LoansContract);
let contract_client: LoansContractClient = LoansContractClient::new(&e, &contract_id);
let mock_borrow_contract_id = e.register_contract(None, MockBorrowContract);
let mock_collateral_contract_id = e.register_contract(None, MockCollateralContract);
let reflector_id: String = String::from_str(&e, "CBKZFI26PDCZUJ5HYYKVB5BWCNYUSNA5LVL4R2JTRVSOB4XEP7Y34OPN");
let reflector_address: Address = Address::from_string(&reflector_id);
e.register_contract(&reflector_address, MockValueContract);

contract_client.initialize(&user, &10_i128, &mock_borrow_contract_id, &1000_i128, &mock_collateral_contract_id);
*/
//TODO: study how to create loan_pools for testing as one can not initialize without it. for now this should pass but it doesn't test anything
Teolhyn marked this conversation as resolved.
Show resolved Hide resolved
}
}
1 change: 1 addition & 0 deletions contracts/loan_manager/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#![no_std]

mod contract;
mod oracle;
mod positions;
mod storage_types;
3 changes: 3 additions & 0 deletions contracts/loan_manager/src/oracle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
use soroban_sdk::contractimport;

contractimport!(file = "../../target/wasm32-unknown-unknown/release/reflector_oracle.wasm");
Teolhyn marked this conversation as resolved.
Show resolved Hide resolved
5 changes: 2 additions & 3 deletions contracts/loan_manager/src/positions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ pub fn init_loan(
borrowed_from: Address,
collateral: i128,
collateral_from: Address,
health_factor: i128,
) {
// Here this part has to call health factor calculator but for now:
let health_factor: i32 = 1000;
write_positions(
e,
addr,
Expand All @@ -29,7 +28,7 @@ fn write_positions(
borrowed_from: Address,
collateral: i128,
collateral_from: Address,
health_factor: i32,
health_factor: i128,
) {
let key = LoansDataKey::Loan(addr);
// Initialize the map with the environment
Expand Down
Loading
Loading