1use super::{
2 contracts::ISafe,
3 rpc_provider,
4 service::{SafeServiceOpts, SafeTransaction},
5 transaction::SafeSendOpts,
6};
7use alloy_primitives::{Address, B256};
8use alloy_rpc_types::Log;
9use alloy_sol_types::{SolCall, SolEvent};
10use clap::Args;
11use eyre::{Context, Result, ensure};
12use foundry_cli::json::print_scalar;
13use foundry_common::sh_status;
14
15#[derive(Args, Debug)]
17pub struct ExecuteArgs {
18 safe: Address,
20
21 safe_tx_hash: B256,
23
24 #[arg(long, default_value = "1")]
26 confirmations: u64,
27
28 #[arg(long, env = "ETH_TIMEOUT")]
30 timeout: Option<u64>,
31
32 #[arg(long, env = "ETH_POLL_INTERVAL")]
34 poll_interval: Option<u64>,
35
36 #[command(flatten)]
37 service: Box<SafeServiceOpts>,
38
39 #[command(flatten)]
40 send: SafeSendOpts,
41}
42
43impl ExecuteArgs {
44 pub(super) async fn run(self) -> Result<()> {
45 let Self { safe, safe_tx_hash, confirmations, timeout, poll_interval, service, send } =
46 self;
47 let (provider, chain_id) = rpc_provider(&send.rpc).await?;
48 let transaction = service.get_transaction(chain_id, "v2", safe_tx_hash).await?;
49 ensure!(
50 !transaction.is_executed && transaction.transaction_hash.is_none(),
51 "Safe transaction has already been executed{}",
52 transaction
53 .transaction_hash
54 .map(|hash| format!(" onchain as {hash}"))
55 .unwrap_or_default()
56 );
57
58 transaction.verify_hash(safe, &provider).await?;
59 let transaction_nonce = SafeTransaction::number(&transaction.nonce, "nonce")?;
60 let current_nonce = ISafe::new(safe, &provider)
61 .nonce()
62 .call()
63 .await
64 .wrap_err("failed to read Safe nonce")?;
65 ensure!(
66 transaction_nonce == current_nonce,
67 "Safe transaction nonce {transaction_nonce} does not match current Safe nonce {current_nonce}"
68 );
69 transaction.show_transaction_summary()?;
70 let signatures = transaction.packed_signatures()?;
71
72 sh_status!("Executing Safe transaction {safe_tx_hash}")?;
73 let calldata = ISafe::execTransactionCall {
74 to: transaction.to,
75 value: SafeTransaction::number(&transaction.value, "value")?,
76 data: transaction.data.clone(),
77 operation: transaction.operation,
78 safeTxGas: SafeTransaction::number(&transaction.safe_tx_gas, "safeTxGas")?,
79 baseGas: SafeTransaction::number(&transaction.base_gas, "baseGas")?,
80 gasPrice: SafeTransaction::number(&transaction.gas_price, "gasPrice")?,
81 gasToken: transaction.gas_token,
82 refundReceiver: transaction.refund_receiver,
83 signatures,
84 }
85 .abi_encode()
86 .into();
87 let result = send
88 .send(safe, calldata, confirmations, timeout, poll_interval)
89 .await
90 .wrap_err("failed to submit Safe transaction")?;
91 ensure!(
92 execution_succeeded(&result.logs, safe, safe_tx_hash)?,
93 "Safe inner transaction failed"
94 );
95 print_scalar(result.tx_hash)
96 }
97}
98
99fn execution_succeeded(logs: &[Log], safe: Address, safe_tx_hash: B256) -> Result<bool> {
101 logs.iter()
102 .rev()
103 .filter(|log| log.address() == safe)
104 .find_map(|log| {
105 if let Ok(event) = ISafe::ExecutionSuccess::decode_log(&log.inner)
106 && event.txHash == safe_tx_hash
107 {
108 return Some(true);
109 }
110 if let Ok(event) = ISafe::ExecutionFailure::decode_log(&log.inner)
111 && event.txHash == safe_tx_hash
112 {
113 return Some(false);
114 }
115 None
116 })
117 .ok_or_else(|| eyre::eyre!("Safe execution receipt did not emit a matching result event"))
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123 use alloy_primitives::{LogData, U256};
124
125 fn log(safe: Address, data: LogData) -> Log {
126 Log { inner: alloy_primitives::Log { address: safe, data }, ..Default::default() }
127 }
128
129 #[test]
130 fn uses_last_matching_safe_execution_event() {
131 let safe = Address::repeat_byte(1);
132 let hash = B256::repeat_byte(2);
133 let success = || {
134 log(
135 safe,
136 ISafe::ExecutionSuccess { txHash: hash, payment: U256::ZERO }.encode_log_data(),
137 )
138 };
139 let failure = || {
140 log(
141 safe,
142 ISafe::ExecutionFailure { txHash: hash, payment: U256::ZERO }.encode_log_data(),
143 )
144 };
145
146 assert!(execution_succeeded(&[failure(), success()], safe, hash).unwrap());
147 assert!(!execution_succeeded(&[success(), failure()], safe, hash).unwrap());
148 }
149}