2022-02-22 04:31:18 -08:00
|
|
|
use anchor_lang::prelude::*;
|
|
|
|
use fixed::types::I80F48;
|
|
|
|
|
2022-03-15 07:08:53 -07:00
|
|
|
use super::{TokenAccount, TokenIndex};
|
2022-03-11 00:57:30 -08:00
|
|
|
use crate::util::checked_math as cm;
|
2022-02-23 01:15:33 -08:00
|
|
|
|
2022-02-22 04:31:18 -08:00
|
|
|
#[account(zero_copy)]
|
2022-03-07 07:16:34 -08:00
|
|
|
pub struct Bank {
|
2022-02-26 03:17:20 -08:00
|
|
|
pub group: Pubkey,
|
|
|
|
pub mint: Pubkey,
|
|
|
|
pub vault: Pubkey,
|
2022-02-28 05:44:08 -08:00
|
|
|
pub oracle: Pubkey,
|
2022-02-26 03:17:20 -08:00
|
|
|
|
2022-02-22 05:23:13 -08:00
|
|
|
/// the index used to scale the value of an IndexedPosition
|
2022-02-23 01:09:01 -08:00
|
|
|
/// TODO: should always be >= 0, add checks?
|
2022-02-22 05:23:13 -08:00
|
|
|
pub deposit_index: I80F48,
|
|
|
|
pub borrow_index: I80F48,
|
2022-02-23 01:09:01 -08:00
|
|
|
|
|
|
|
/// total deposits/borrows, for utilization
|
|
|
|
pub indexed_total_deposits: I80F48,
|
|
|
|
pub indexed_total_borrows: I80F48,
|
2022-02-22 04:31:18 -08:00
|
|
|
// todo: multi-leg interest
|
|
|
|
// pub optimal_util: I80F48,
|
|
|
|
// pub optimal_rate: I80F48,
|
|
|
|
// pub max_rate: I80F48,
|
2022-02-26 03:04:42 -08:00
|
|
|
|
|
|
|
// This is a _lot_ of bytes (64) - seems unnecessary
|
|
|
|
// (could maybe store them in one byte each, as an informal U1F7?
|
|
|
|
// that could store values between 0-2 and converting to I80F48 would be a cheap expand+shift)
|
|
|
|
pub maint_asset_weight: I80F48,
|
|
|
|
pub init_asset_weight: I80F48,
|
|
|
|
pub maint_liab_weight: I80F48,
|
|
|
|
pub init_liab_weight: I80F48,
|
|
|
|
|
2022-03-04 04:33:27 -08:00
|
|
|
// Collection of all fractions-of-native-tokens that got rounded away
|
|
|
|
pub dust: I80F48,
|
|
|
|
|
2022-02-26 03:04:42 -08:00
|
|
|
// Index into TokenInfo on the group
|
|
|
|
pub token_index: TokenIndex,
|
2022-02-22 05:23:13 -08:00
|
|
|
}
|
2022-02-22 04:31:18 -08:00
|
|
|
|
2022-03-07 07:16:34 -08:00
|
|
|
impl Bank {
|
2022-02-25 04:10:51 -08:00
|
|
|
pub fn native_total_deposits(&self) -> I80F48 {
|
|
|
|
self.deposit_index * self.indexed_total_deposits
|
|
|
|
}
|
|
|
|
|
2022-03-04 04:33:27 -08:00
|
|
|
/// Returns whether the position is active
|
2022-03-15 07:08:53 -07:00
|
|
|
pub fn deposit(&mut self, position: &mut TokenAccount, native_amount: u64) -> Result<bool> {
|
2022-02-23 01:09:01 -08:00
|
|
|
let mut native_amount = I80F48::from_num(native_amount);
|
|
|
|
let native_position = position.native(self);
|
|
|
|
|
|
|
|
if native_position.is_negative() {
|
2022-03-11 00:57:30 -08:00
|
|
|
let new_native_position = cm!(native_position + native_amount);
|
2022-03-20 22:58:11 -07:00
|
|
|
if !new_native_position.is_positive() {
|
2022-03-04 04:33:27 -08:00
|
|
|
// pay back borrows only, leaving a negative position
|
2022-03-11 00:57:30 -08:00
|
|
|
let indexed_change = cm!(native_amount / self.borrow_index + I80F48::DELTA);
|
|
|
|
self.indexed_total_borrows = cm!(self.indexed_total_borrows - indexed_change);
|
|
|
|
position.indexed_value = cm!(position.indexed_value + indexed_change);
|
|
|
|
return Ok(true);
|
2022-03-15 06:44:47 -07:00
|
|
|
} else if new_native_position < I80F48::ONE && !position.is_in_use() {
|
2022-03-04 04:33:27 -08:00
|
|
|
// if there's less than one token deposited, zero the position
|
2022-03-11 00:57:30 -08:00
|
|
|
self.dust = cm!(self.dust + new_native_position);
|
|
|
|
self.indexed_total_borrows =
|
|
|
|
cm!(self.indexed_total_borrows + position.indexed_value);
|
2022-03-04 04:33:27 -08:00
|
|
|
position.indexed_value = I80F48::ZERO;
|
2022-03-11 00:57:30 -08:00
|
|
|
return Ok(false);
|
2022-02-23 01:09:01 -08:00
|
|
|
}
|
|
|
|
|
2022-03-04 04:33:27 -08:00
|
|
|
// pay back all borrows
|
2022-03-11 00:57:30 -08:00
|
|
|
self.indexed_total_borrows = cm!(self.indexed_total_borrows + position.indexed_value); // position.value is negative
|
2022-02-23 01:09:01 -08:00
|
|
|
position.indexed_value = I80F48::ZERO;
|
2022-03-04 04:33:27 -08:00
|
|
|
// deposit the rest
|
2022-03-11 00:57:30 -08:00
|
|
|
native_amount = cm!(native_amount + native_position);
|
2022-02-23 01:09:01 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// add to deposits
|
2022-03-04 04:33:27 -08:00
|
|
|
// Adding DELTA to amount/index helps because (amount/index)*index <= amount, but
|
|
|
|
// we want to ensure that users can withdraw the same amount they have deposited, so
|
|
|
|
// (amount/index + delta)*index >= amount is a better guarantee.
|
2022-03-11 00:57:30 -08:00
|
|
|
let indexed_change = cm!(native_amount / self.deposit_index + I80F48::DELTA);
|
|
|
|
self.indexed_total_deposits = cm!(self.indexed_total_deposits + indexed_change);
|
|
|
|
position.indexed_value = cm!(position.indexed_value + indexed_change);
|
2022-03-04 04:33:27 -08:00
|
|
|
|
2022-03-11 00:57:30 -08:00
|
|
|
Ok(true)
|
2022-02-23 01:09:01 -08:00
|
|
|
}
|
|
|
|
|
2022-03-04 04:33:27 -08:00
|
|
|
/// Returns whether the position is active
|
2022-03-15 07:08:53 -07:00
|
|
|
pub fn withdraw(&mut self, position: &mut TokenAccount, native_amount: u64) -> Result<bool> {
|
2022-02-23 01:09:01 -08:00
|
|
|
let mut native_amount = I80F48::from_num(native_amount);
|
|
|
|
let native_position = position.native(self);
|
|
|
|
|
|
|
|
if native_position.is_positive() {
|
2022-03-11 00:57:30 -08:00
|
|
|
let new_native_position = cm!(native_position - native_amount);
|
2022-03-04 04:33:27 -08:00
|
|
|
if !new_native_position.is_negative() {
|
2022-02-23 01:09:01 -08:00
|
|
|
// withdraw deposits only
|
2022-03-15 06:44:47 -07:00
|
|
|
if new_native_position < I80F48::ONE && !position.is_in_use() {
|
2022-03-04 04:33:27 -08:00
|
|
|
// zero the account collecting the leftovers in `dust`
|
2022-03-11 00:57:30 -08:00
|
|
|
self.dust = cm!(self.dust + new_native_position);
|
|
|
|
self.indexed_total_deposits =
|
|
|
|
cm!(self.indexed_total_deposits - position.indexed_value);
|
2022-03-04 04:33:27 -08:00
|
|
|
position.indexed_value = I80F48::ZERO;
|
2022-03-11 00:57:30 -08:00
|
|
|
return Ok(false);
|
2022-03-04 04:33:27 -08:00
|
|
|
} else {
|
2022-03-15 06:44:47 -07:00
|
|
|
// withdraw some deposits leaving a positive balance
|
2022-03-11 00:57:30 -08:00
|
|
|
let indexed_change = cm!(native_amount / self.deposit_index);
|
|
|
|
self.indexed_total_deposits = cm!(self.indexed_total_deposits - indexed_change);
|
|
|
|
position.indexed_value = cm!(position.indexed_value - indexed_change);
|
|
|
|
return Ok(true);
|
2022-03-04 04:33:27 -08:00
|
|
|
}
|
2022-02-23 01:09:01 -08:00
|
|
|
}
|
|
|
|
|
2022-03-04 04:33:27 -08:00
|
|
|
// withdraw all deposits
|
2022-03-11 00:57:30 -08:00
|
|
|
self.indexed_total_deposits = cm!(self.indexed_total_deposits - position.indexed_value);
|
2022-02-23 01:09:01 -08:00
|
|
|
position.indexed_value = I80F48::ZERO;
|
2022-03-04 04:33:27 -08:00
|
|
|
// borrow the rest
|
|
|
|
native_amount = -new_native_position;
|
2022-02-23 01:09:01 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// add to borrows
|
2022-03-11 00:57:30 -08:00
|
|
|
let indexed_change = cm!(native_amount / self.borrow_index);
|
|
|
|
self.indexed_total_borrows = cm!(self.indexed_total_borrows + indexed_change);
|
|
|
|
position.indexed_value = cm!(position.indexed_value - indexed_change);
|
2022-03-04 04:33:27 -08:00
|
|
|
|
2022-03-11 00:57:30 -08:00
|
|
|
Ok(true)
|
2022-02-22 05:23:13 -08:00
|
|
|
}
|
2022-03-15 06:44:47 -07:00
|
|
|
|
2022-03-15 07:08:53 -07:00
|
|
|
pub fn change(&mut self, position: &mut TokenAccount, native_amount: i64) -> Result<bool> {
|
2022-03-15 06:44:47 -07:00
|
|
|
if native_amount >= 0 {
|
|
|
|
self.deposit(position, native_amount as u64)
|
|
|
|
} else {
|
|
|
|
self.withdraw(position, (-native_amount) as u64)
|
|
|
|
}
|
|
|
|
}
|
2022-02-22 04:31:18 -08:00
|
|
|
}
|
2022-03-20 22:58:11 -07:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use bytemuck::Zeroable;
|
|
|
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn change() -> Result<()> {
|
|
|
|
let epsilon = I80F48::from_bits(1);
|
|
|
|
let cases = [
|
|
|
|
(-10.1, 1),
|
|
|
|
(-10.1, 10),
|
|
|
|
(-10.1, 11),
|
|
|
|
(-10.1, 50),
|
|
|
|
(-2.0, 2),
|
|
|
|
(-2.0, 3),
|
|
|
|
(-0.1, 1),
|
|
|
|
(0.0, 1),
|
|
|
|
(0.1, 1),
|
|
|
|
(10.1, -1),
|
|
|
|
(10.1, -9),
|
|
|
|
(10.1, -10),
|
|
|
|
(10.1, -11),
|
|
|
|
(1.0, -1),
|
|
|
|
(0.1, -1),
|
|
|
|
(0.0, -1),
|
|
|
|
(-0.1, -1),
|
|
|
|
(-1.1, -10),
|
|
|
|
];
|
|
|
|
|
|
|
|
for is_in_use in [false, true] {
|
|
|
|
for (start, change) in cases {
|
|
|
|
println!(
|
|
|
|
"testing: in use: {}, start: {}, change: {}",
|
|
|
|
is_in_use, start, change
|
|
|
|
);
|
|
|
|
|
|
|
|
//
|
|
|
|
// SETUP
|
|
|
|
//
|
|
|
|
|
|
|
|
let mut bank = Bank::zeroed();
|
|
|
|
bank.deposit_index = I80F48::from_num(100.0);
|
|
|
|
bank.borrow_index = I80F48::from_num(10.0);
|
|
|
|
let indexed = |v: I80F48, b: &Bank| {
|
|
|
|
if v > 0 {
|
|
|
|
v / b.deposit_index
|
|
|
|
} else {
|
|
|
|
v / b.borrow_index
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut account = TokenAccount {
|
|
|
|
indexed_value: I80F48::ZERO,
|
|
|
|
token_index: 0,
|
|
|
|
in_use_count: if is_in_use { 1 } else { 0 },
|
|
|
|
};
|
|
|
|
|
|
|
|
account.indexed_value = indexed(I80F48::from_num(start), &bank);
|
|
|
|
if start >= 0.0 {
|
|
|
|
bank.indexed_total_deposits = account.indexed_value;
|
|
|
|
} else {
|
|
|
|
bank.indexed_total_borrows = -account.indexed_value;
|
|
|
|
}
|
|
|
|
|
|
|
|
// get the rounded start value
|
|
|
|
let start_native = account.native(&bank);
|
|
|
|
|
|
|
|
//
|
|
|
|
// TEST
|
|
|
|
//
|
|
|
|
|
|
|
|
let is_active = bank.change(&mut account, change)?;
|
|
|
|
|
|
|
|
let mut expected_native = start_native + I80F48::from(change);
|
|
|
|
if expected_native >= 0.0 && expected_native < 1.0 && !is_in_use {
|
|
|
|
assert!(!is_active);
|
|
|
|
assert_eq!(bank.dust, expected_native);
|
|
|
|
expected_native = I80F48::ZERO;
|
|
|
|
} else {
|
|
|
|
assert!(is_active);
|
|
|
|
assert_eq!(bank.dust, I80F48::ZERO);
|
|
|
|
}
|
|
|
|
let expected_indexed = indexed(expected_native, &bank);
|
|
|
|
|
|
|
|
// at most one epsilon error in the resulting indexed value
|
|
|
|
assert!((account.indexed_value - expected_indexed).abs() <= epsilon);
|
|
|
|
|
|
|
|
if account.indexed_value.is_positive() {
|
|
|
|
assert_eq!(bank.indexed_total_deposits, account.indexed_value);
|
|
|
|
assert_eq!(bank.indexed_total_borrows, I80F48::ZERO);
|
|
|
|
} else {
|
|
|
|
assert_eq!(bank.indexed_total_deposits, I80F48::ZERO);
|
|
|
|
assert_eq!(bank.indexed_total_borrows, -account.indexed_value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|