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 = match operation {
289 Operation::DelegateCall => {
290 self.simulate_via_exec_transaction(&simulator, to, value, data, operation)
292 .await?
293 }
294 Operation::Call => {
295 simulator
296 .simulate_call(self.safe.address, to, value, data, operation)
297 .await?
298 }
299 };
300
301 if !result.success {
302 return Err(Error::SimulationReverted {
303 reason: result
304 .revert_reason
305 .unwrap_or_else(|| "Unknown".to_string()),
306 });
307 }
308
309 self.simulation_result = Some(result);
310 Ok(self)
311 }
312
313 async fn simulate_via_exec_transaction(
318 &self,
319 simulator: &ForkSimulator<P>,
320 to: Address,
321 value: U256,
322 data: Bytes,
323 operation: Operation,
324 ) -> Result<SimulationResult> {
325 let nonce = self.safe.nonce().await?;
327
328 let safe_tx_gas = U256::from(10_000_000);
330
331 let params = SafeTxParams {
333 to,
334 value,
335 data: data.clone(),
336 operation,
337 safe_tx_gas,
338 base_gas: U256::ZERO,
339 gas_price: U256::ZERO,
340 gas_token: Address::ZERO,
341 refund_receiver: Address::ZERO,
342 nonce,
343 };
344
345 let tx_hash = compute_safe_transaction_hash(
347 self.safe.config.chain_id,
348 self.safe.address,
349 ¶ms,
350 );
351
352 let signature = sign_hash(&self.safe.signer, tx_hash).await?;
354
355 let exec_call = ISafe::execTransactionCall {
357 to: params.to,
358 value: params.value,
359 data: params.data,
360 operation: params.operation.as_u8(),
361 safeTxGas: params.safe_tx_gas,
362 baseGas: params.base_gas,
363 gasPrice: params.gas_price,
364 gasToken: params.gas_token,
365 refundReceiver: params.refund_receiver,
366 signatures: signature,
367 };
368
369 let exec_data = Bytes::from(exec_call.abi_encode());
370
371 simulator
373 .simulate_call(
374 self.safe.signer.address(), self.safe.address, U256::ZERO, exec_data,
378 Operation::Call, )
380 .await
381 }
382
383 pub fn simulation_result(&self) -> Option<&SimulationResult> {
385 self.simulation_result.as_ref()
386 }
387
388 pub async fn execute(self) -> Result<ExecutionResult> {
394 if self.calls.is_empty() {
395 return Err(Error::NoCalls);
396 }
397
398 let (to, value, data, operation) = self.build_call_params()?;
399
400 let nonce = self.safe.nonce().await?;
402
403 let safe_tx_gas = match (&self.simulation_result, self.safe_tx_gas) {
405 (_, Some(gas)) => gas, (Some(sim), None) => {
407 let gas_used = sim.gas_used;
409 U256::from(gas_used + gas_used / 10)
410 }
411 (None, None) => {
412 use alloy::network::TransactionBuilder;
414 let tx_request = <AnyNetwork as alloy::network::Network>::TransactionRequest::default()
415 .with_from(self.safe.address)
416 .with_to(to)
417 .with_value(value)
418 .with_input(data.clone());
419
420 let estimated = self
421 .safe
422 .provider
423 .estimate_gas(tx_request)
424 .await
425 .map_err(|e| Error::Provider(format!("gas estimation failed: {}", e)))?;
426
427 U256::from(estimated + estimated / 10)
429 }
430 };
431
432 let params = SafeTxParams {
434 to,
435 value,
436 data: data.clone(),
437 operation,
438 safe_tx_gas,
439 base_gas: U256::ZERO,
440 gas_price: U256::ZERO,
441 gas_token: Address::ZERO,
442 refund_receiver: Address::ZERO,
443 nonce,
444 };
445
446 let tx_hash = compute_safe_transaction_hash(
448 self.safe.config.chain_id,
449 self.safe.address,
450 ¶ms,
451 );
452
453 let signature = sign_hash(&self.safe.signer, tx_hash).await?;
455
456 let exec_call = ISafe::execTransactionCall {
458 to: params.to,
459 value: params.value,
460 data: params.data,
461 operation: params.operation.as_u8(),
462 safeTxGas: params.safe_tx_gas,
463 baseGas: params.base_gas,
464 gasPrice: params.gas_price,
465 gasToken: params.gas_token,
466 refundReceiver: params.refund_receiver,
467 signatures: signature,
468 };
469
470 let safe_contract = ISafe::new(self.safe.address, &self.safe.provider);
472
473 let builder = safe_contract.execTransaction(
474 exec_call.to,
475 exec_call.value,
476 exec_call.data,
477 exec_call.operation,
478 exec_call.safeTxGas,
479 exec_call.baseGas,
480 exec_call.gasPrice,
481 exec_call.gasToken,
482 exec_call.refundReceiver,
483 exec_call.signatures,
484 );
485
486 let pending_tx = builder
487 .send()
488 .await
489 .map_err(|e| Error::ExecutionFailed {
490 reason: e.to_string(),
491 })?;
492
493 let receipt = pending_tx
494 .get_receipt()
495 .await
496 .map_err(|e| Error::ExecutionFailed {
497 reason: e.to_string(),
498 })?;
499
500 let success = receipt.status();
502
503 Ok(ExecutionResult {
504 tx_hash: receipt.transaction_hash,
505 success,
506 })
507 }
508
509 fn build_call_params(&self) -> Result<(Address, U256, Bytes, Operation)> {
510 if self.calls.len() == 1 {
511 let call = &self.calls[0];
513 Ok((call.to, call.value, call.data.clone(), Operation::Call))
514 } else {
515 let multisend_data = encode_multisend_data(&self.calls);
517
518 let (multisend_address, calldata) = if self.use_call_only {
519 let call = IMultiSendCallOnly::multiSendCall {
520 transactions: multisend_data,
521 };
522 (
523 self.safe.addresses().multi_send_call_only,
524 Bytes::from(call.abi_encode()),
525 )
526 } else {
527 let call = IMultiSend::multiSendCall {
528 transactions: multisend_data,
529 };
530 (
531 self.safe.addresses().multi_send,
532 Bytes::from(call.abi_encode()),
533 )
534 };
535
536 Ok((multisend_address, U256::ZERO, calldata, Operation::DelegateCall))
538 }
539 }
540}
541
542#[cfg(test)]
543mod tests {
544 #[allow(unused_imports)]
545 use super::*;
546 use alloy::primitives::address;
547
548 #[test]
549 fn test_call_params_single() {
550 let _addr = address!("0x1234567890123456789012345678901234567890");
553 }
554}