use alloy::{
primitives::{Address, BlockNumber, U256},
providers::DynProvider,
sol,
};
use serde::Deserialize;
use crate::{
Result,
quoter::{Quoter, RateDirection},
token::{Token, identity::TokenIdentifier},
};
sol! {
#[sol(rpc)]
contract ERC4626 {
function asset() public view returns (address);
function convertToAssets(uint256 shares) public view returns (uint256);
function convertToShares(uint256 assets) public view returns (uint256);
}
}
#[derive(Debug, Deserialize, PartialEq, Clone)]
pub struct ERC4626Config {
pub vault_address: Address,
}
#[derive(Debug, Clone)]
pub struct ERC4626Quoter {
pub vault_address: Token,
pub token_address: Token,
}
impl ERC4626Quoter {
pub async fn new(vault_address: Address, provider: &DynProvider) -> Result<Self> {
let vault = ERC4626::new(vault_address, provider);
let token_address = vault.asset().call().await?;
let token_address = Token::new(token_address.into(), provider).await?;
let vault_address = Token::new(vault_address.into(), provider).await?;
Ok(Self {
vault_address,
token_address,
})
}
}
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl Quoter for ERC4626Quoter {
fn identity(&self) -> String {
format!("erc4626:{}", self.vault_address.identifier)
}
fn tokens(&self) -> (TokenIdentifier, TokenIdentifier) {
(
self.vault_address.identifier.clone(),
self.token_address.identifier.clone(),
)
}
async fn rate(
&self,
amount_in: U256,
direction: RateDirection,
block: BlockNumber,
provider: &DynProvider,
) -> Result<U256> {
let vault = ERC4626::new(
self.vault_address
.address()
.ok_or(crate::error::EthPricesError::MissingVaultAddress)?,
provider,
);
Ok(match direction {
RateDirection::Forward => {
vault
.convertToAssets(amount_in)
.block(block.into())
.call()
.await?
}
RateDirection::Reverse => {
vault
.convertToShares(amount_in)
.block(block.into())
.call()
.await?
}
})
}
}
#[cfg(test)]
mod tests {
use alloy::primitives::address;
use super::*;
use crate::{tests::get_test_provider, token::Token};
#[tokio::test]
async fn test_get_rate() {
let block = 25000000;
let vault_address = address!("0x0c6aec603d48eBf1cECc7b247a2c3DA08b398DC1");
let provider = get_test_provider().await;
let quoter = ERC4626Quoter::new(vault_address, &provider).await.unwrap();
let token_a = Token::new(quoter.vault_address.identifier.clone(), &provider)
.await
.unwrap();
let token_a_amount = token_a.nominal_amount();
let forward_rate = quoter
.rate(token_a_amount, RateDirection::Forward, block, &provider)
.await
.unwrap();
let token_b = Token::new(quoter.token_address.identifier.clone(), &provider)
.await
.unwrap();
let token_b_amount = token_b.nominal_amount();
let reverse_rate = quoter
.rate(token_b_amount, RateDirection::Reverse, block, &provider)
.await
.unwrap();
assert_eq!(forward_rate, U256::from(1023479));
assert_eq!(reverse_rate, U256::from(977058994841501187u64));
let precision = 4;
println!(
"forward_rate: {:?} = {:?}",
token_a.format_amount(token_a_amount, precision),
token_b.format_amount(forward_rate, precision)
);
println!(
"reverse_rate: {:?} = {:?}",
token_b.format_amount(token_b_amount, precision),
token_a.format_amount(reverse_rate, precision)
);
}
}