1use alloy::network::AnyNetwork;
4use alloy::network::primitives::ReceiptResponse;
5use alloy::primitives::{Address, Bytes, TxHash, U256};
6use alloy::providers::Provider;
7use alloy::signers::local::PrivateKeySigner;
8use alloy::sol_types::SolCall;
9
10use crate::chain::{ChainAddresses, ChainConfig};
11use crate::contracts::{IMultiSend, IMultiSendCallOnly, ISafe};
12use crate::encoding::{compute_safe_transaction_hash, encode_multisend_data, SafeTxParams};
13use crate::error::{Error, Result};
14use crate::signing::sign_hash;
15use crate::simulation::{ForkSimulator, SimulationResult};
16use crate::types::{Call, Operation, SafeCall, TypedCall};
17
18#[derive(Debug, Clone)]
20pub struct ExecutionResult {
21 pub tx_hash: TxHash,
23 pub success: bool,
25}
26
27pub struct Safe<P> {
29 provider: P,
31 signer: PrivateKeySigner,
33 address: Address,
35 config: ChainConfig,
37}
38
39impl<P> Safe<P>
40where
41 P: Provider<AnyNetwork> + Clone + 'static,
42{
43 pub fn new(provider: P, signer: PrivateKeySigner, address: Address, config: ChainConfig) -> Self {
45 Self {
46 provider,
47 signer,
48 address,
49 config,
50 }
51 }
52
53 pub async fn connect(provider: P, signer: PrivateKeySigner, address: Address) -> Result<Self> {
55 let chain_id = provider
56 .get_chain_id()
57 .await
58 .map_err(|e| Error::Provider(e.to_string()))?;
59
60 let config = ChainConfig::new(chain_id);
61 Ok(Self::new(provider, signer, address, config))
62 }
63
64 pub fn address(&self) -> Address {
66 self.address
67 }
68
69 pub fn config(&self) -> &ChainConfig {
71 &self.config
72 }
73
74 pub fn addresses(&self) -> &ChainAddresses {
76 &self.config.addresses
77 }
78
79 pub fn provider(&self) -> &P {
81 &self.provider
82 }
83
84 pub fn signer_address(&self) -> Address {
86 self.signer.address()
87 }
88
89 pub async fn nonce(&self) -> Result<U256> {
91 let safe = ISafe::new(self.address, &self.provider);
92 let nonce = safe
93 .nonce()
94 .call()
95 .await
96 .map_err(|e| Error::Fetch {
97 what: "nonce",
98 reason: e.to_string(),
99 })?;
100 Ok(nonce)
101 }
102
103 pub async fn threshold(&self) -> Result<u64> {
105 let safe = ISafe::new(self.address, &self.provider);
106 let threshold = safe
107 .getThreshold()
108 .call()
109 .await
110 .map_err(|e| Error::Fetch {
111 what: "threshold",
112 reason: e.to_string(),
113 })?;
114 Ok(threshold.to::<u64>())
115 }
116
117 pub async fn owners(&self) -> Result<Vec<Address>> {
119 let safe = ISafe::new(self.address, &self.provider);
120 let owners = safe
121 .getOwners()
122 .call()
123 .await
124 .map_err(|e| Error::Fetch {
125 what: "owners",
126 reason: e.to_string(),
127 })?;
128 Ok(owners)
129 }
130
131 pub async fn is_owner(&self, address: Address) -> Result<bool> {
133 let safe = ISafe::new(self.address, &self.provider);
134 let is_owner = safe
135 .isOwner(address)
136 .call()
137 .await
138 .map_err(|e| Error::Fetch {
139 what: "is_owner",
140 reason: e.to_string(),
141 })?;
142 Ok(is_owner)
143 }
144
145 pub async fn verify_single_owner(&self) -> Result<()> {
147 let threshold = self.threshold().await?;
148 if threshold != 1 {
149 return Err(Error::InvalidThreshold { threshold });
150 }
151
152 let is_owner = self.is_owner(self.signer.address()).await?;
153 if !is_owner {
154 return Err(Error::NotOwner {
155 signer: self.signer.address(),
156 safe: self.address,
157 });
158 }
159
160 Ok(())
161 }
162
163 pub fn multicall(&self) -> MulticallBuilder<'_, P> {
165 MulticallBuilder::new(self)
166 }
167
168 pub async fn execute_single(
170 &self,
171 to: Address,
172 value: U256,
173 data: Bytes,
174 operation: Operation,
175 ) -> Result<ExecutionResult> {
176 self.multicall()
177 .add_raw(to, value, data)
178 .with_operation(operation)
179 .simulate()
180 .await?
181 .execute()
182 .await
183 }
184}
185
186pub struct MulticallBuilder<'a, P> {
188 safe: &'a Safe<P>,
189 calls: Vec<Call>,
190 use_call_only: bool,
191 safe_tx_gas: Option<U256>,
192 operation: Operation,
193 simulation_result: Option<SimulationResult>,
194}
195
196impl<'a, P> MulticallBuilder<'a, P>
197where
198 P: Provider<AnyNetwork> + Clone + 'static,
199{
200 fn new(safe: &'a Safe<P>) -> Self {
201 MulticallBuilder {
202 safe,
203 calls: Vec::new(),
204 use_call_only: false,
205 safe_tx_gas: None,
206 operation: Operation::DelegateCall, simulation_result: None,
208 }
209 }
210 pub fn add_typed<C: SolCall + Clone>(mut self, to: Address, call: C) -> Self {
212 let typed_call = TypedCall::new(to, call);
213 self.calls.push(Call::new(
214 typed_call.to(),
215 typed_call.value,
216 typed_call.data(),
217 ));
218 self
219 }
220
221 pub fn add_typed_with_value<C: SolCall + Clone>(
223 mut self,
224 to: Address,
225 call: C,
226 value: U256,
227 ) -> Self {
228 let typed_call = TypedCall::new(to, call).with_value(value);
229 self.calls.push(Call::new(
230 typed_call.to(),
231 typed_call.value,
232 typed_call.data(),
233 ));
234 self
235 }
236
237 pub fn add_raw(mut self, to: Address, value: U256, data: impl Into<Bytes>) -> Self {
239 self.calls.push(Call::new(to, value, data));
240 self
241 }
242
243 pub fn add(mut self, call: impl SafeCall) -> Self {
245 self.calls.push(Call {
246 to: call.to(),
247 value: call.value(),
248 data: call.data(),
249 operation: call.operation(),
250 });
251 self
252 }
253
254 pub fn call_only(mut self) -> Self {
256 self.use_call_only = true;
257 self
258 }
259
260 pub fn with_operation(mut self, operation: Operation) -> Self {
262 self.operation = operation;
263 self
264 }
265
266 pub fn with_safe_tx_gas(mut self, gas: U256) -> Self {
268 self.safe_tx_gas = Some(gas);
269 self
270 }
271
272 pub async fn simulate(mut self) -> Result<Self> {
277 if self.calls.is_empty() {
278 return Err(Error::NoCalls);
279 }
280
281 let (to, value, data, operation) = self.build_call_params()?;
282
283 let simulator = ForkSimulator::new(self.safe.provider.clone(), self.safe.config.chain_id);
284
285 let result = simulator
286 .simulate_call(self.safe.address, to, value, data, operation)
287 .await?;
288
289 if !result.success {
290 return Err(Error::SimulationReverted {
291 reason: result
292 .revert_reason
293 .unwrap_or_else(|| "Unknown".to_string()),
294 });
295 }
296
297 self.simulation_result = Some(result);
298 Ok(self)
299 }
300
301 pub fn simulation_result(&self) -> Option<&SimulationResult> {
303 self.simulation_result.as_ref()
304 }
305
306 pub async fn execute(self) -> Result<ExecutionResult> {
312 if self.calls.is_empty() {
313 return Err(Error::NoCalls);
314 }
315
316 let (to, value, data, operation) = self.build_call_params()?;
317
318 let nonce = self.safe.nonce().await?;
320
321 let safe_tx_gas = match (&self.simulation_result, self.safe_tx_gas) {
323 (_, Some(gas)) => gas, (Some(sim), None) => {
325 let gas_used = sim.gas_used;
327 U256::from(gas_used + gas_used / 10)
328 }
329 (None, None) => {
330 use alloy::network::TransactionBuilder;
332 let tx_request = <AnyNetwork as alloy::network::Network>::TransactionRequest::default()
333 .with_from(self.safe.address)
334 .with_to(to)
335 .with_value(value)
336 .with_input(data.clone());
337
338 let estimated = self
339 .safe
340 .provider
341 .estimate_gas(tx_request)
342 .await
343 .map_err(|e| Error::Provider(format!("gas estimation failed: {}", e)))?;
344
345 U256::from(estimated + estimated / 10)
347 }
348 };
349
350 let params = SafeTxParams {
352 to,
353 value,
354 data: data.clone(),
355 operation,
356 safe_tx_gas,
357 base_gas: U256::ZERO,
358 gas_price: U256::ZERO,
359 gas_token: Address::ZERO,
360 refund_receiver: Address::ZERO,
361 nonce,
362 };
363
364 let tx_hash = compute_safe_transaction_hash(
366 self.safe.config.chain_id,
367 self.safe.address,
368 ¶ms,
369 );
370
371 let signature = sign_hash(&self.safe.signer, tx_hash).await?;
373
374 let exec_call = ISafe::execTransactionCall {
376 to: params.to,
377 value: params.value,
378 data: params.data,
379 operation: params.operation.as_u8(),
380 safeTxGas: params.safe_tx_gas,
381 baseGas: params.base_gas,
382 gasPrice: params.gas_price,
383 gasToken: params.gas_token,
384 refundReceiver: params.refund_receiver,
385 signatures: signature,
386 };
387
388 let safe_contract = ISafe::new(self.safe.address, &self.safe.provider);
390
391 let builder = safe_contract.execTransaction(
392 exec_call.to,
393 exec_call.value,
394 exec_call.data,
395 exec_call.operation,
396 exec_call.safeTxGas,
397 exec_call.baseGas,
398 exec_call.gasPrice,
399 exec_call.gasToken,
400 exec_call.refundReceiver,
401 exec_call.signatures,
402 );
403
404 let pending_tx = builder
405 .send()
406 .await
407 .map_err(|e| Error::ExecutionFailed {
408 reason: e.to_string(),
409 })?;
410
411 let receipt = pending_tx
412 .get_receipt()
413 .await
414 .map_err(|e| Error::ExecutionFailed {
415 reason: e.to_string(),
416 })?;
417
418 let success = receipt.status();
420
421 Ok(ExecutionResult {
422 tx_hash: receipt.transaction_hash,
423 success,
424 })
425 }
426
427 fn build_call_params(&self) -> Result<(Address, U256, Bytes, Operation)> {
428 if self.calls.len() == 1 {
429 let call = &self.calls[0];
431 Ok((call.to, call.value, call.data.clone(), Operation::Call))
432 } else {
433 let multisend_data = encode_multisend_data(&self.calls);
435
436 let (multisend_address, calldata) = if self.use_call_only {
437 let call = IMultiSendCallOnly::multiSendCall {
438 transactions: multisend_data,
439 };
440 (
441 self.safe.addresses().multi_send_call_only,
442 Bytes::from(call.abi_encode()),
443 )
444 } else {
445 let call = IMultiSend::multiSendCall {
446 transactions: multisend_data,
447 };
448 (
449 self.safe.addresses().multi_send,
450 Bytes::from(call.abi_encode()),
451 )
452 };
453
454 Ok((multisend_address, U256::ZERO, calldata, Operation::DelegateCall))
456 }
457 }
458}
459
460#[cfg(test)]
461mod tests {
462 #[allow(unused_imports)]
463 use super::*;
464 use alloy::primitives::address;
465
466 #[test]
467 fn test_call_params_single() {
468 let _addr = address!("0x1234567890123456789012345678901234567890");
471 }
472}