Skip to main content

morpho_rs_contracts/
erc4626_client.rs

1//! Shared ERC-4626 client trait and macro for vault clients.
2//!
3//! This module provides a trait with default implementations for ERC-4626 view functions,
4//! and a macro for transaction methods that return `PreparedCall`.
5
6#![allow(async_fn_in_trait)]
7
8use alloy::primitives::{Address, U256};
9
10use crate::erc4626::IERC4626;
11use crate::error::{ContractError, Result};
12use crate::provider::HttpProvider;
13
14/// Trait for ERC-4626 vault client functionality.
15///
16/// Provides default implementations for all ERC-4626 view functions.
17/// Implementors only need to provide `provider()` and `signer_address()`.
18pub trait Erc4626Client {
19    /// Returns a reference to the HTTP provider.
20    fn provider(&self) -> &HttpProvider;
21
22    /// Returns the signer's address.
23    fn signer_address(&self) -> Address;
24
25    /// Get the underlying asset address of a vault.
26    async fn get_asset(&self, vault: Address) -> Result<Address> {
27        let contract = IERC4626::new(vault, self.provider());
28        let result = contract
29            .asset()
30            .call()
31            .await
32            .map_err(|e| ContractError::TransactionFailed(format!("Failed to get asset: {}", e)))?;
33        Ok(result)
34    }
35
36    /// Get the total assets managed by a vault.
37    async fn total_assets(&self, vault: Address) -> Result<U256> {
38        let contract = IERC4626::new(vault, self.provider());
39        let result = contract.totalAssets().call().await.map_err(|e| {
40            ContractError::TransactionFailed(format!("Failed to get total assets: {}", e))
41        })?;
42        Ok(result)
43    }
44
45    /// Convert an asset amount to shares.
46    async fn convert_to_shares(&self, vault: Address, assets: U256) -> Result<U256> {
47        let contract = IERC4626::new(vault, self.provider());
48        let result = contract.convertToShares(assets).call().await.map_err(|e| {
49            ContractError::TransactionFailed(format!("Failed to convert to shares: {}", e))
50        })?;
51        Ok(result)
52    }
53
54    /// Convert a share amount to assets.
55    async fn convert_to_assets(&self, vault: Address, shares: U256) -> Result<U256> {
56        let contract = IERC4626::new(vault, self.provider());
57        let result = contract.convertToAssets(shares).call().await.map_err(|e| {
58            ContractError::TransactionFailed(format!("Failed to convert to assets: {}", e))
59        })?;
60        Ok(result)
61    }
62
63    /// Get the maximum deposit amount for a receiver.
64    async fn max_deposit(&self, vault: Address, receiver: Address) -> Result<U256> {
65        let contract = IERC4626::new(vault, self.provider());
66        let result = contract.maxDeposit(receiver).call().await.map_err(|e| {
67            ContractError::TransactionFailed(format!("Failed to get max deposit: {}", e))
68        })?;
69        Ok(result)
70    }
71
72    /// Get the maximum withdraw amount for an owner.
73    async fn max_withdraw(&self, vault: Address, owner: Address) -> Result<U256> {
74        let contract = IERC4626::new(vault, self.provider());
75        let result = contract.maxWithdraw(owner).call().await.map_err(|e| {
76            ContractError::TransactionFailed(format!("Failed to get max withdraw: {}", e))
77        })?;
78        Ok(result)
79    }
80
81    /// Get the maximum mint amount (in shares) for a receiver.
82    async fn max_mint(&self, vault: Address, receiver: Address) -> Result<U256> {
83        let contract = IERC4626::new(vault, self.provider());
84        let result = contract.maxMint(receiver).call().await.map_err(|e| {
85            ContractError::TransactionFailed(format!("Failed to get max mint: {}", e))
86        })?;
87        Ok(result)
88    }
89
90    /// Get the maximum redeem amount (in shares) for an owner.
91    async fn max_redeem(&self, vault: Address, owner: Address) -> Result<U256> {
92        let contract = IERC4626::new(vault, self.provider());
93        let result = contract.maxRedeem(owner).call().await.map_err(|e| {
94            ContractError::TransactionFailed(format!("Failed to get max redeem: {}", e))
95        })?;
96        Ok(result)
97    }
98
99    /// Preview the shares that would be received for a deposit.
100    async fn preview_deposit(&self, vault: Address, assets: U256) -> Result<U256> {
101        let contract = IERC4626::new(vault, self.provider());
102        let result = contract.previewDeposit(assets).call().await.map_err(|e| {
103            ContractError::TransactionFailed(format!("Failed to preview deposit: {}", e))
104        })?;
105        Ok(result)
106    }
107
108    /// Preview the assets required to mint a specific amount of shares.
109    async fn preview_mint(&self, vault: Address, shares: U256) -> Result<U256> {
110        let contract = IERC4626::new(vault, self.provider());
111        let result = contract.previewMint(shares).call().await.map_err(|e| {
112            ContractError::TransactionFailed(format!("Failed to preview mint: {}", e))
113        })?;
114        Ok(result)
115    }
116
117    /// Preview the shares that would be burned for a withdrawal.
118    async fn preview_withdraw(&self, vault: Address, assets: U256) -> Result<U256> {
119        let contract = IERC4626::new(vault, self.provider());
120        let result = contract.previewWithdraw(assets).call().await.map_err(|e| {
121            ContractError::TransactionFailed(format!("Failed to preview withdraw: {}", e))
122        })?;
123        Ok(result)
124    }
125
126    /// Preview the assets that would be received for redeeming shares.
127    async fn preview_redeem(&self, vault: Address, shares: U256) -> Result<U256> {
128        let contract = IERC4626::new(vault, self.provider());
129        let result = contract.previewRedeem(shares).call().await.map_err(|e| {
130            ContractError::TransactionFailed(format!("Failed to preview redeem: {}", e))
131        })?;
132        Ok(result)
133    }
134}
135
136/// Macro to implement ERC-4626 transaction methods on a client struct.
137///
138/// This macro generates `deposit`, `withdraw`, `mint`, and `redeem` methods
139/// that return `PreparedCall` types. It's needed because trait methods cannot
140/// return types with lifetime parameters tied to `self`.
141///
142/// # Usage
143///
144/// ```rust,ignore
145/// impl_erc4626_transactions!(MyVaultClient);
146/// ```
147#[macro_export]
148macro_rules! impl_erc4626_transactions {
149    ($client:ty) => {
150        impl $client {
151            /// Create a prepared deposit transaction.
152            /// Returns a `PreparedCall` that can be sent or used with `MulticallBuilder`.
153            pub fn deposit(
154                &self,
155                vault: alloy::primitives::Address,
156                amount: alloy::primitives::U256,
157                receiver: alloy::primitives::Address,
158            ) -> $crate::prepared_call::PreparedCall<'_, $crate::erc4626::IERC4626::depositCall> {
159                let call = $crate::erc4626::IERC4626::depositCall {
160                    assets: amount,
161                    receiver,
162                };
163                $crate::prepared_call::PreparedCall::new(
164                    vault,
165                    call,
166                    alloy::primitives::U256::ZERO,
167                    &self.provider,
168                )
169            }
170
171            /// Create a prepared withdraw transaction.
172            /// Returns a `PreparedCall` that can be sent or used with `MulticallBuilder`.
173            pub fn withdraw(
174                &self,
175                vault: alloy::primitives::Address,
176                amount: alloy::primitives::U256,
177                receiver: alloy::primitives::Address,
178                owner: alloy::primitives::Address,
179            ) -> $crate::prepared_call::PreparedCall<'_, $crate::erc4626::IERC4626::withdrawCall>
180            {
181                let call = $crate::erc4626::IERC4626::withdrawCall {
182                    assets: amount,
183                    receiver,
184                    owner,
185                };
186                $crate::prepared_call::PreparedCall::new(
187                    vault,
188                    call,
189                    alloy::primitives::U256::ZERO,
190                    &self.provider,
191                )
192            }
193
194            /// Create a prepared mint transaction.
195            /// Returns a `PreparedCall` that can be sent or used with `MulticallBuilder`.
196            pub fn mint(
197                &self,
198                vault: alloy::primitives::Address,
199                shares: alloy::primitives::U256,
200                receiver: alloy::primitives::Address,
201            ) -> $crate::prepared_call::PreparedCall<'_, $crate::erc4626::IERC4626::mintCall> {
202                let call = $crate::erc4626::IERC4626::mintCall { shares, receiver };
203                $crate::prepared_call::PreparedCall::new(
204                    vault,
205                    call,
206                    alloy::primitives::U256::ZERO,
207                    &self.provider,
208                )
209            }
210
211            /// Create a prepared redeem transaction.
212            /// Returns a `PreparedCall` that can be sent or used with `MulticallBuilder`.
213            pub fn redeem(
214                &self,
215                vault: alloy::primitives::Address,
216                shares: alloy::primitives::U256,
217                receiver: alloy::primitives::Address,
218                owner: alloy::primitives::Address,
219            ) -> $crate::prepared_call::PreparedCall<'_, $crate::erc4626::IERC4626::redeemCall>
220            {
221                let call = $crate::erc4626::IERC4626::redeemCall {
222                    shares,
223                    receiver,
224                    owner,
225                };
226                $crate::prepared_call::PreparedCall::new(
227                    vault,
228                    call,
229                    alloy::primitives::U256::ZERO,
230                    &self.provider,
231                )
232            }
233        }
234    };
235}