Skip to content

Commit

Permalink
apply requested changes
Browse files Browse the repository at this point in the history
  • Loading branch information
rouzwelt committed Oct 23, 2024
1 parent cdadbf5 commit 31f54d7
Show file tree
Hide file tree
Showing 7 changed files with 223 additions and 83 deletions.
67 changes: 12 additions & 55 deletions crates/subgraph/src/apy.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use crate::{
types::common::{Erc20, Order, Trade},
utils::{one_18, to_18_decimals, year_18, DAY},
utils::{one_18, to_18_decimals, year_18},
vol::{get_vaults_vol, VaultVolume},
OrderbookSubgraphClientError,
};
use alloy::primitives::{
utils::{parse_units, ParseUnits},
I256, U256,
};
use chrono::TimeDelta;
use serde::{Deserialize, Serialize};
use std::{
collections::{BTreeMap, HashMap},
Expand Down Expand Up @@ -256,7 +257,8 @@ pub fn get_token_vaults_apy(
.iter()
.filter(|v| {
u64::from_str(&v.timestamp.0).unwrap()
<= u64::from_str(&first_trade.timestamp.0).unwrap() + DAY
<= u64::from_str(&first_trade.timestamp.0).unwrap()
+ TimeDelta::days(1).num_seconds() as u64
})
.collect::<Vec<&&Trade>>()[0];

Expand Down Expand Up @@ -354,18 +356,18 @@ fn get_pairs_ratio(order_apy: &OrderAPY, trades: &[Trade]) -> HashMap<TokenPair,
input: input.token.clone(),
output: output.token.clone(),
};
let reverse_pair_as_key = TokenPair {
let inverse_pair_as_key = TokenPair {
input: output.token.clone(),
output: input.token.clone(),
};
// if not same io token and ratio map doesnt already include them
if input.token != output.token
&& !(pair_ratio_map.contains_key(&pair_as_key)
|| pair_ratio_map.contains_key(&reverse_pair_as_key))
|| pair_ratio_map.contains_key(&inverse_pair_as_key))
{
// find this pairs(io or oi) latest tradetrades from list of order's
// trades, the calculate the pair ratio (in amount/out amount) and
// its reverse from the latest trade that involes these 2 tokens.
// its inverse from the latest trade that involes these 2 tokens.
// this assumes the trades are already in desc order by timestamp which
// is the case when used this lib query to get them
let ratio = trades
Expand All @@ -379,62 +381,17 @@ fn get_pairs_ratio(order_apy: &OrderAPY, trades: &[Trade]) -> HashMap<TokenPair,
.and_then(|latest_trade| {
// convert input and output amounts to 18 decimals point
// and then calculate the pair ratio
to_18_decimals(
ParseUnits::U256(
U256::from_str(&latest_trade.input_vault_balance_change.amount.0)
.unwrap(),
),
latest_trade
.input_vault_balance_change
.vault
.token
.decimals
.as_ref()
.map(|v| v.0.as_str())
.unwrap_or("18"),
latest_trade.ratio().zip(latest_trade.inverse_ratio()).map(
|(ratio, inverse_ratio)| {
[I256::from_raw(ratio), I256::from_raw(inverse_ratio)]
},
)
.ok()
.zip(
to_18_decimals(
ParseUnits::U256(
U256::from_str(
&latest_trade.output_vault_balance_change.amount.0[1..],
)
.unwrap(),
),
latest_trade
.output_vault_balance_change
.vault
.token
.decimals
.as_ref()
.map(|v| v.0.as_str())
.unwrap_or("18"),
)
.ok(),
)
.map(|(input_amount, output_amount)| {
[
// io ratio
input_amount
.get_signed()
.saturating_mul(one_18().get_signed())
.checked_div(output_amount.get_signed())
.unwrap_or(I256::MAX),
// oi ratio
output_amount
.get_signed()
.saturating_mul(one_18().get_signed())
.checked_div(input_amount.get_signed())
.unwrap_or(I256::MAX),
]
})
});

// io
pair_ratio_map.insert(pair_as_key, ratio.map(|v| v[0]));
// oi
pair_ratio_map.insert(reverse_pair_as_key, ratio.map(|v| v[1]));
pair_ratio_map.insert(inverse_pair_as_key, ratio.map(|v| v[1]));
}
}
}
Expand Down
188 changes: 188 additions & 0 deletions crates/subgraph/src/types/impls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
use super::common::*;
use crate::utils::{one_18, to_18_decimals};
use alloy::primitives::{
utils::{ParseUnits, UnitsError},
I256, U256,
};
use std::str::FromStr;

impl Trade {
/// Converts this trade's input to 18 point decimals in U256/I256
pub fn input_to_18_decimals(&self) -> Result<ParseUnits, UnitsError> {
to_18_decimals(
ParseUnits::I256(I256::from_str(&self.input_vault_balance_change.amount.0).unwrap()),
self.input_vault_balance_change
.vault
.token
.decimals
.as_ref()
.map(|v| v.0.as_str())
.unwrap_or("18"),
)
}

/// Converts this trade's output to 18 point decimals in U256/I256
pub fn output_to_18_decimals(&self) -> Result<ParseUnits, UnitsError> {
to_18_decimals(
ParseUnits::I256(I256::from_str(&self.output_vault_balance_change.amount.0).unwrap()),
self.output_vault_balance_change
.vault
.token
.decimals
.as_ref()
.map(|v| v.0.as_str())
.unwrap_or("18"),
)
}

/// Calculates the trade I/O ratio
pub fn ratio(&self) -> Option<U256> {
Some(
self.input_to_18_decimals()
.ok()?
.get_absolute()
.saturating_mul(one_18().get_absolute())
.checked_div(
self.output_to_18_decimals()
.ok()?
.get_signed()
.saturating_neg()
.try_into()
.ok()?,
)
.unwrap_or(U256::MAX),
)
}

/// Calculates the trade O/I ratio (inverse)
pub fn inverse_ratio(&self) -> Option<U256> {
Some(
TryInto::<U256>::try_into(
self.output_to_18_decimals()
.ok()?
.get_signed()
.saturating_neg(),
)
.ok()?
.saturating_mul(one_18().get_absolute())
.checked_div(self.input_to_18_decimals().ok()?.get_absolute())
.unwrap_or(U256::MAX),
)
}
}

#[cfg(test)]
mod test {
use super::*;
use crate::types::common::{
BigInt, Bytes, Orderbook, TradeEvent, TradeStructPartialOrder, TradeVaultBalanceChange,
Transaction, VaultBalanceChangeVault,
};
use alloy::primitives::Address;

#[test]
fn test_input_to_18_decimals() {
let result = get_trade().input_to_18_decimals().unwrap();
let expected = U256::from_str("3000000000000000000").unwrap();
assert_eq!(result.get_absolute(), expected);
}

#[test]
fn test_output_to_18_decimals() {
let result = get_trade().output_to_18_decimals().unwrap();
let expected = I256::from_str("-6000000000000000000").unwrap();
assert_eq!(result.get_signed(), expected);
}

#[test]
fn test_ratio() {
let result = get_trade().ratio().unwrap();
let expected = U256::from_str("500000000000000000").unwrap();
assert_eq!(result, expected);
}

#[test]
fn test_inverse_ratio() {
let result = get_trade().inverse_ratio().unwrap();
let expected = U256::from_str("2000000000000000000").unwrap();
assert_eq!(result, expected);
}

// helper to get trade struct
fn get_trade() -> Trade {
let token_address = Address::from_slice(&[0x11u8; 20]);
let token = Erc20 {
id: Bytes(token_address.to_string()),
address: Bytes(token_address.to_string()),
name: Some("Token1".to_string()),
symbol: Some("Token1".to_string()),
decimals: Some(BigInt(6.to_string())),
};
let input_trade_vault_balance_change = TradeVaultBalanceChange {
id: Bytes("".to_string()),
__typename: "".to_string(),
amount: BigInt("3000000".to_string()),
new_vault_balance: BigInt("".to_string()),
old_vault_balance: BigInt("".to_string()),
vault: VaultBalanceChangeVault {
id: Bytes("".to_string()),
vault_id: BigInt("".to_string()),
token: token.clone(),
},
timestamp: BigInt("".to_string()),
transaction: Transaction {
id: Bytes("".to_string()),
from: Bytes("".to_string()),
block_number: BigInt("".to_string()),
timestamp: BigInt("".to_string()),
},
orderbook: Orderbook {
id: Bytes("".to_string()),
},
};
let output_trade_vault_balance_change = TradeVaultBalanceChange {
id: Bytes("".to_string()),
__typename: "".to_string(),
amount: BigInt("-6000000".to_string()),
new_vault_balance: BigInt("".to_string()),
old_vault_balance: BigInt("".to_string()),
vault: VaultBalanceChangeVault {
id: Bytes("".to_string()),
vault_id: BigInt("".to_string()),
token: token.clone(),
},
timestamp: BigInt("".to_string()),
transaction: Transaction {
id: Bytes("".to_string()),
from: Bytes("".to_string()),
block_number: BigInt("".to_string()),
timestamp: BigInt("".to_string()),
},
orderbook: Orderbook {
id: Bytes("".to_string()),
},
};
Trade {
id: Bytes("".to_string()),
trade_event: TradeEvent {
transaction: Transaction {
id: Bytes("".to_string()),
from: Bytes("".to_string()),
block_number: BigInt("".to_string()),
timestamp: BigInt("".to_string()),
},
sender: Bytes("".to_string()),
},
output_vault_balance_change: output_trade_vault_balance_change,
input_vault_balance_change: input_trade_vault_balance_change,
order: TradeStructPartialOrder {
id: Bytes("".to_string()),
order_hash: Bytes("".to_string()),
},
timestamp: BigInt("".to_string()),
orderbook: Orderbook {
id: Bytes("".to_string()),
},
}
}
}
1 change: 1 addition & 0 deletions crates/subgraph/src/types/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod common;
pub mod impls;
pub mod order;
pub mod order_detail_traits;
pub mod order_trade;
Expand Down
7 changes: 3 additions & 4 deletions crates/subgraph/src/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
use alloy::primitives::utils::{format_units, parse_units, ParseUnits, Unit, UnitsError};
use chrono::TimeDelta;

mod order_id;
mod slice_list;

pub use order_id::*;
pub use slice_list::*;

pub const DAY: u64 = 60 * 60 * 24;
pub const YEAR: u64 = DAY * 365;

/// Returns 18 point decimals 1 as I256/U256
pub fn one_18() -> ParseUnits {
parse_units("1", 18).unwrap()
}

/// Returns YEAR as 18 point decimals as I256/U256
pub fn year_18() -> ParseUnits {
parse_units(&YEAR.to_string(), 18).unwrap()
parse_units(&TimeDelta::days(365).num_seconds().to_string(), 18).unwrap()
}

/// Converts a U256/I256 value to a 18 fixed point U256/I256 given the decimals point
Expand Down Expand Up @@ -44,6 +42,7 @@ mod test {

#[test]
fn test_year_18_decimals() {
const YEAR: u64 = 60 * 60 * 24 * 365;
let result = year_18();
let expected_signed = I256::try_from(YEAR)
.unwrap()
Expand Down
14 changes: 2 additions & 12 deletions tauri-app/src/lib/components/tables/OrderAPY.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import { subgraphUrl } from '$lib/stores/settings';
import { TableBodyCell, TableHeadCell } from 'flowbite-svelte';
import ApyTimeFilters from '../charts/APYTimeFilters.svelte';
import { formatUnits } from 'viem';
import { bigintString18ToPercentage } from '$lib/utils/number';
export let id: string;
Expand All @@ -20,16 +20,6 @@
getNextPageParam: () => undefined,
enabled: !!$subgraphUrl,
});
function formatApyToPercentage(value: string): string {
let valueString = formatUnits(BigInt(value) * 100n, 18);
const index = valueString.indexOf('.');
if (index > -1) {
// 5 point decimals to show on UI
valueString = valueString.substring(0, index + 6);
}
return valueString;
}
</script>

<TanstackAppTable query={orderApy} emptyMessage="APY Unavailable" rowHoverable={false}>
Expand All @@ -43,7 +33,7 @@
<svelte:fragment slot="bodyRow" let:item>
<TableBodyCell tdClass="break-all px-4 py-2" data-testid="apy-field">
{item.denominatedApy
? formatApyToPercentage(item.denominatedApy.apy) +
? bigintString18ToPercentage(item.denominatedApy.apy, 5) +
'% in ' +
(item.denominatedApy.token.symbol ??
item.denominatedApy.token.name ??
Expand Down
Loading

0 comments on commit 31f54d7

Please sign in to comment.