morpho_rs_contracts/
vault_v1.rs1use alloy::{
4 network::EthereumWallet,
5 primitives::{Address, U256},
6 providers::ProviderBuilder,
7 rpc::types::TransactionReceipt,
8 signers::local::PrivateKeySigner,
9};
10
11use crate::erc20::IERC20;
12use crate::erc4626::IERC4626;
13use crate::error::{ContractError, Result};
14use crate::provider::HttpProvider;
15
16pub struct VaultV1TransactionClient {
18 provider: HttpProvider,
19 signer_address: Address,
20}
21
22impl VaultV1TransactionClient {
23 pub fn new(rpc_url: &str, private_key: &str) -> Result<Self> {
25 let signer: PrivateKeySigner = private_key
26 .parse()
27 .map_err(|_| ContractError::InvalidPrivateKey)?;
28 let signer_address = signer.address();
29 let wallet = EthereumWallet::from(signer);
30
31 let url: url::Url = rpc_url
32 .parse()
33 .map_err(|e| ContractError::RpcConnection(format!("{}", e)))?;
34
35 let provider = ProviderBuilder::new()
36 .with_recommended_fillers()
37 .wallet(wallet)
38 .on_http(url);
39
40 Ok(Self {
41 provider,
42 signer_address,
43 })
44 }
45
46 pub async fn get_asset(&self, vault: Address) -> Result<Address> {
48 let contract = IERC4626::new(vault, &self.provider);
49 let result = contract
50 .asset()
51 .call()
52 .await
53 .map_err(|e| ContractError::TransactionFailed(format!("Failed to get asset: {}", e)))?;
54 Ok(result._0)
55 }
56
57 pub async fn get_decimals(&self, token: Address) -> Result<u8> {
59 let contract = IERC20::new(token, &self.provider);
60 let result = contract.decimals().call().await.map_err(|e| {
61 ContractError::TransactionFailed(format!("Failed to get decimals: {}", e))
62 })?;
63 Ok(result._0)
64 }
65
66 pub async fn get_balance(&self, token: Address, owner: Address) -> Result<U256> {
68 let contract = IERC20::new(token, &self.provider);
69 let result = contract.balanceOf(owner).call().await.map_err(|e| {
70 ContractError::TransactionFailed(format!("Failed to get balance: {}", e))
71 })?;
72 Ok(result._0)
73 }
74
75 pub async fn get_allowance(
77 &self,
78 token: Address,
79 owner: Address,
80 spender: Address,
81 ) -> Result<U256> {
82 let contract = IERC20::new(token, &self.provider);
83 let result = contract.allowance(owner, spender).call().await.map_err(|e| {
84 ContractError::TransactionFailed(format!("Failed to get allowance: {}", e))
85 })?;
86 Ok(result._0)
87 }
88
89 pub async fn approve_if_needed(
92 &self,
93 token: Address,
94 spender: Address,
95 amount: U256,
96 ) -> Result<Option<TransactionReceipt>> {
97 let current_allowance = self
98 .get_allowance(token, self.signer_address, spender)
99 .await?;
100
101 if current_allowance >= amount {
102 return Ok(None);
103 }
104
105 let contract = IERC20::new(token, &self.provider);
106 let tx = contract.approve(spender, amount);
107
108 let pending = tx.send().await.map_err(|e| {
109 ContractError::TransactionFailed(format!("Failed to send approval: {}", e))
110 })?;
111
112 let receipt = pending.get_receipt().await.map_err(|e| {
113 ContractError::TransactionFailed(format!("Failed to get approval receipt: {}", e))
114 })?;
115
116 Ok(Some(receipt))
117 }
118
119 pub async fn deposit(
122 &self,
123 vault: Address,
124 amount: U256,
125 receiver: Address,
126 ) -> Result<TransactionReceipt> {
127 let contract = IERC4626::new(vault, &self.provider);
128 let tx = contract.deposit(amount, receiver);
129
130 let pending = tx.send().await.map_err(|e| {
131 ContractError::TransactionFailed(format!("Failed to send deposit: {}", e))
132 })?;
133
134 let receipt = pending.get_receipt().await.map_err(|e| {
135 ContractError::TransactionFailed(format!("Failed to get deposit receipt: {}", e))
136 })?;
137
138 Ok(receipt)
139 }
140
141 pub async fn withdraw(
144 &self,
145 vault: Address,
146 amount: U256,
147 receiver: Address,
148 owner: Address,
149 ) -> Result<TransactionReceipt> {
150 let contract = IERC4626::new(vault, &self.provider);
151 let tx = contract.withdraw(amount, receiver, owner);
152
153 let pending = tx.send().await.map_err(|e| {
154 ContractError::TransactionFailed(format!("Failed to send withdraw: {}", e))
155 })?;
156
157 let receipt = pending.get_receipt().await.map_err(|e| {
158 ContractError::TransactionFailed(format!("Failed to get withdraw receipt: {}", e))
159 })?;
160
161 Ok(receipt)
162 }
163
164 pub fn signer_address(&self) -> Address {
166 self.signer_address
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173
174 #[test]
175 fn test_invalid_private_key() {
176 let result = VaultV1TransactionClient::new("http://localhost:8545", "invalid_key");
177 assert!(matches!(result, Err(ContractError::InvalidPrivateKey)));
178 }
179
180 #[test]
181 fn test_invalid_rpc_url() {
182 let private_key = "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
184 let result = VaultV1TransactionClient::new("not a valid url", private_key);
185 assert!(matches!(result, Err(ContractError::RpcConnection(_))));
186 }
187
188 #[test]
189 fn test_valid_construction() {
190 let private_key = "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
191 let result = VaultV1TransactionClient::new("http://localhost:8545", private_key);
192 assert!(result.is_ok());
193 }
194}