morpho_rs_contracts/
vault_v2.rs1use alloy::{
4 network::EthereumWallet,
5 primitives::{Address, U256},
6 providers::ProviderBuilder,
7 signers::local::PrivateKeySigner,
8};
9
10use crate::erc20::IERC20;
11use crate::erc4626::IERC4626;
12use crate::error::{ContractError, Result};
13use crate::prepared_call::PreparedCall;
14use crate::provider::HttpProvider;
15
16pub struct VaultV2TransactionClient {
18 provider: HttpProvider,
19 signer_address: Address,
20}
21
22impl VaultV2TransactionClient {
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 .wallet(wallet)
37 .connect_http(url);
38
39 Ok(Self {
40 provider,
41 signer_address,
42 })
43 }
44
45 pub async fn get_asset(&self, vault: Address) -> Result<Address> {
47 let contract = IERC4626::new(vault, &self.provider);
48 let result = contract
49 .asset()
50 .call()
51 .await
52 .map_err(|e| ContractError::TransactionFailed(format!("Failed to get asset: {}", e)))?;
53 Ok(result)
54 }
55
56 pub async fn get_decimals(&self, token: Address) -> Result<u8> {
58 let contract = IERC20::new(token, &self.provider);
59 let result = contract.decimals().call().await.map_err(|e| {
60 ContractError::TransactionFailed(format!("Failed to get decimals: {}", e))
61 })?;
62 Ok(result)
63 }
64
65 pub async fn get_balance(&self, token: Address, owner: Address) -> Result<U256> {
67 let contract = IERC20::new(token, &self.provider);
68 let result = contract.balanceOf(owner).call().await.map_err(|e| {
69 ContractError::TransactionFailed(format!("Failed to get balance: {}", e))
70 })?;
71 Ok(result)
72 }
73
74 pub async fn get_allowance(
76 &self,
77 token: Address,
78 owner: Address,
79 spender: Address,
80 ) -> Result<U256> {
81 let contract = IERC20::new(token, &self.provider);
82 let result = contract.allowance(owner, spender).call().await.map_err(|e| {
83 ContractError::TransactionFailed(format!("Failed to get allowance: {}", e))
84 })?;
85 Ok(result)
86 }
87
88 pub fn approve(
91 &self,
92 token: Address,
93 spender: Address,
94 amount: U256,
95 ) -> PreparedCall<'_, IERC20::approveCall> {
96 let call = IERC20::approveCall { spender, amount };
97 PreparedCall::new(token, call, U256::ZERO, &self.provider)
98 }
99
100 pub async fn approve_if_needed(
103 &self,
104 token: Address,
105 spender: Address,
106 amount: U256,
107 ) -> Result<Option<PreparedCall<'_, IERC20::approveCall>>> {
108 let current_allowance = self
109 .get_allowance(token, self.signer_address, spender)
110 .await?;
111
112 if current_allowance >= amount {
113 return Ok(None);
114 }
115
116 Ok(Some(self.approve(token, spender, amount)))
117 }
118
119 pub fn deposit(
122 &self,
123 vault: Address,
124 amount: U256,
125 receiver: Address,
126 ) -> PreparedCall<'_, IERC4626::depositCall> {
127 let call = IERC4626::depositCall { assets: amount, receiver };
128 PreparedCall::new(vault, call, U256::ZERO, &self.provider)
129 }
130
131 pub fn withdraw(
134 &self,
135 vault: Address,
136 amount: U256,
137 receiver: Address,
138 owner: Address,
139 ) -> PreparedCall<'_, IERC4626::withdrawCall> {
140 let call = IERC4626::withdrawCall { assets: amount, receiver, owner };
141 PreparedCall::new(vault, call, U256::ZERO, &self.provider)
142 }
143
144 pub fn signer_address(&self) -> Address {
146 self.signer_address
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn test_invalid_private_key() {
156 let result = VaultV2TransactionClient::new("http://localhost:8545", "invalid_key");
157 assert!(matches!(result, Err(ContractError::InvalidPrivateKey)));
158 }
159
160 #[test]
161 fn test_invalid_rpc_url() {
162 let private_key = "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
164 let result = VaultV2TransactionClient::new("not a valid url", private_key);
165 assert!(matches!(result, Err(ContractError::RpcConnection(_))));
166 }
167
168 #[test]
169 fn test_valid_construction() {
170 let private_key = "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
171 let result = VaultV2TransactionClient::new("http://localhost:8545", private_key);
172 assert!(result.is_ok());
173 }
174}