use std::ops::{Add, Div, Mul, Sub};
use alloy::{
primitives::{Address, FixedBytes, U256},
providers::Provider,
transports::TransportError,
};
use num_traits::{FromPrimitive, One, ToPrimitive};
use crate::hyperevm::{
DynProvider,
morpho::contracts::{
IIrm,
IMetaMorpho::{self, IMetaMorphoInstance},
IMorpho::{self, IMorphoInstance},
Market, MarketParams,
},
};
pub mod contracts;
pub type MarketId = FixedBytes<32>;
#[derive(Debug, Clone)]
pub struct PoolApy<T128> {
pub params: MarketParams,
pub market: Market,
pub borrow: T128,
pub supply: T128,
}
#[derive(Debug, Clone)]
pub struct VaultApy<T128> {
pub components: Vec<VaultSupply<T128>>,
pub fee: U256,
pub total_deposits: U256,
}
#[derive(Debug, Clone)]
pub struct VaultSupply<T128> {
pub supplied_shares: U256,
pub pool: PoolApy<T128>,
pub supply_apy: T128,
}
impl<T128> VaultApy<T128>
where
T128: ToPrimitive,
{
#[must_use]
pub fn apy<T256, F>(&self, convert: F) -> T256
where
T256: Add<T256, Output = T256>
+ Sub<T256, Output = T256>
+ Mul<T256, Output = T256>
+ Div<T256, Output = T256>
+ One
+ Copy,
F: Fn(U256) -> T256,
{
let zero = convert(U256::ZERO);
let wad = convert(U256::from(1_000_000_000_000_000_000u128));
if self.total_deposits.is_zero() {
return zero;
}
let total_deposits = convert(self.total_deposits);
let fee = convert(self.fee);
let fee_multiplier = (wad - fee) / wad;
let gross_apy = self
.components
.iter()
.filter_map(|component| {
if component.pool.market.totalSupplyShares == 0 {
return None;
}
let supplied_shares = convert(component.supplied_shares);
let total_supply_assets =
convert(U256::from(component.pool.market.totalSupplyAssets));
let total_supply_shares =
convert(U256::from(component.pool.market.totalSupplyShares));
let supplied_assets = supplied_shares * total_supply_assets / total_supply_shares;
let supply_apy = convert(U256::from(component.supply_apy.to_u128().unwrap()));
Some(supplied_assets * supply_apy / total_deposits)
})
.fold(zero, |acc, x| acc + x);
gross_apy * fee_multiplier / wad
}
#[must_use]
pub fn market_count(&self) -> usize {
self.components.len()
}
}
pub struct Client<P>
where
P: Provider,
{
provider: P,
}
impl Client<DynProvider> {
pub async fn mainnet() -> Result<Self, TransportError> {
let provider = DynProvider::new(super::mainnet().await?);
Ok(Self::new(provider))
}
pub async fn mainnet_with_url(url: &str) -> Result<Self, TransportError> {
let provider = DynProvider::new(super::mainnet_with_url(url).await?);
Ok(Self::new(provider))
}
}
impl<P> Client<P>
where
P: Provider + Clone,
{
pub fn new(provider: P) -> Self {
Self { provider }
}
pub fn provider(&self) -> &P {
&self.provider
}
pub fn instance(&self, address: Address) -> IMorphoInstance<P> {
IMorpho::new(address, self.provider.clone())
}
pub async fn apy<T128, F>(
&self,
address: Address,
market_id: MarketId,
exp: F,
) -> anyhow::Result<PoolApy<T128>>
where
T128: FromPrimitive
+ Add<T128, Output = T128>
+ Sub<T128, Output = T128>
+ Mul<T128, Output = T128>
+ Div<T128, Output = T128>
+ One
+ Copy,
F: FnOnce(T128) -> T128,
{
let morpho = IMorpho::new(address, self.provider.clone());
let (params, market) = self
.provider
.multicall()
.add(morpho.idToMarketParams(market_id))
.add(morpho.market(market_id))
.aggregate()
.await?;
self.apy_with(params, market, exp).await
}
pub async fn apy_with<T128, F>(
&self,
params: impl Into<MarketParams>,
market: impl Into<Market>,
exp: F,
) -> anyhow::Result<PoolApy<T128>>
where
T128: FromPrimitive
+ Sub<T128, Output = T128>
+ Mul<T128, Output = T128>
+ Div<T128, Output = T128>
+ One
+ Copy,
F: FnOnce(T128) -> T128,
{
let params = params.into();
let market = market.into();
anyhow::ensure!(
market.totalSupplyAssets > 0,
"market has no assets supplied"
);
let irm = IIrm::new(params.irm, self.provider.clone());
let rate = irm
.borrowRateView(params.into(), market.into())
.call()
.await?;
let error = || anyhow::anyhow!("unable to convert u128 into Float");
let wad = T128::from_u128(1_000_000_000_000_000_000u128).ok_or_else(error)?;
let seconds_in_a_year = T128::from_u128(31_536_000).ok_or_else(error)?;
let one = T128::one();
let fee = T128::from_u128(market.fee).ok_or_else(error)? / wad;
let utilization = T128::from_u128(market.totalBorrowAssets).ok_or_else(error)?
/ T128::from_u128(market.totalSupplyAssets).ok_or_else(error)?;
let rate = T128::from_u128(rate.to::<u128>()).ok_or_else(error)? / wad;
let borrow_apy = (exp)(rate * seconds_in_a_year) - one;
let supply_apy = borrow_apy * utilization * (one - fee);
Ok(PoolApy {
params,
market,
borrow: borrow_apy,
supply: supply_apy,
})
}
}
pub struct MetaClient<P>
where
P: Provider,
{
provider: P,
}
impl MetaClient<DynProvider> {
pub async fn mainnet() -> Result<Self, TransportError> {
let provider = DynProvider::new(super::mainnet().await?);
Ok(Self::new(provider))
}
pub async fn mainnet_with_url(url: &str) -> Result<Self, TransportError> {
let provider = DynProvider::new(super::mainnet_with_url(url).await?);
Ok(Self::new(provider))
}
}
impl<P> MetaClient<P>
where
P: Provider + Clone,
{
pub fn new(provider: P) -> Self {
Self { provider }
}
pub fn provider(&self) -> &P {
&self.provider
}
pub fn instance(&self, address: Address) -> IMetaMorphoInstance<P> {
IMetaMorpho::new(address, self.provider.clone())
}
pub async fn apy<T128, F>(&self, address: Address, exp: F) -> anyhow::Result<VaultApy<T128>>
where
T128: FromPrimitive
+ Add<T128, Output = T128>
+ Sub<T128, Output = T128>
+ Mul<T128, Output = T128>
+ Div<T128, Output = T128>
+ One
+ Copy,
F: FnOnce(T128) -> T128 + Copy,
{
let error = || anyhow::anyhow!("unable to convert u128 into Float");
let wad = T128::from_u128(1_000_000_000_000_000_000u128).ok_or_else(error)?;
let meta_morpho = IMetaMorpho::new(address, self.provider.clone());
let (fee, supply_queue_len, total_assets, morpho_addr) = self
.provider
.multicall()
.add(meta_morpho.fee())
.add(meta_morpho.supplyQueueLength())
.add(meta_morpho.totalAssets())
.add(meta_morpho.MORPHO())
.aggregate()
.await?;
let supply_queue_len = supply_queue_len.to::<usize>();
let morpho = IMorpho::new(morpho_addr, self.provider.clone());
let mut apy = VaultApy {
components: vec![],
fee: U256::from(fee),
total_deposits: total_assets,
};
for i in 0..supply_queue_len {
let market_id = meta_morpho.supplyQueue(U256::from(i)).call().await?;
let (config, params, market) = self
.provider
.multicall()
.add(meta_morpho.config(market_id))
.add(morpho.idToMarketParams(market_id))
.add(morpho.market(market_id))
.aggregate()
.await?;
if !config.enabled
|| params.irm.is_zero()
|| params.collateralToken.is_zero()
|| params.loanToken.is_zero()
{
continue;
}
let position = morpho
.position(market_id, *meta_morpho.address())
.call()
.await?;
let pool = Client::new(self.provider.clone())
.apy_with::<T128, F>(params, market, exp)
.await?;
let supply_apy = pool.supply * wad;
apy.components.push(VaultSupply {
supplied_shares: position.supplyShares,
pool,
supply_apy,
});
}
Ok(apy)
}
}