morpho_rs_contracts/
vault_v1.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 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 fn approve(
92 &self,
93 token: Address,
94 spender: Address,
95 amount: U256,
96 ) -> PreparedCall<'_, IERC20::approveCall> {
97 let call = IERC20::approveCall { spender, amount };
98 PreparedCall::new(token, call, U256::ZERO, &self.provider)
99 }
100
101 pub async fn approve_if_needed(
104 &self,
105 token: Address,
106 spender: Address,
107 amount: U256,
108 ) -> Result<Option<PreparedCall<'_, IERC20::approveCall>>> {
109 let current_allowance = self
110 .get_allowance(token, self.signer_address, spender)
111 .await?;
112
113 if current_allowance >= amount {
114 return Ok(None);
115 }
116
117 Ok(Some(self.approve(token, spender, amount)))
118 }
119
120 pub fn deposit(
123 &self,
124 vault: Address,
125 amount: U256,
126 receiver: Address,
127 ) -> PreparedCall<'_, IERC4626::depositCall> {
128 let call = IERC4626::depositCall { assets: amount, receiver };
129 PreparedCall::new(vault, call, U256::ZERO, &self.provider)
130 }
131
132 pub fn withdraw(
135 &self,
136 vault: Address,
137 amount: U256,
138 receiver: Address,
139 owner: Address,
140 ) -> PreparedCall<'_, IERC4626::withdrawCall> {
141 let call = IERC4626::withdrawCall { assets: amount, receiver, owner };
142 PreparedCall::new(vault, call, U256::ZERO, &self.provider)
143 }
144
145 pub fn signer_address(&self) -> Address {
147 self.signer_address
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn test_invalid_private_key() {
157 let result = VaultV1TransactionClient::new("http://localhost:8545", "invalid_key");
158 assert!(matches!(result, Err(ContractError::InvalidPrivateKey)));
159 }
160
161 #[test]
162 fn test_invalid_rpc_url() {
163 let private_key = "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
165 let result = VaultV1TransactionClient::new("not a valid url", private_key);
166 assert!(matches!(result, Err(ContractError::RpcConnection(_))));
167 }
168
169 #[test]
170 fn test_valid_construction() {
171 let private_key = "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
172 let result = VaultV1TransactionClient::new("http://localhost:8545", private_key);
173 assert!(result.is_ok());
174 }
175}