cast/cmd/safe/
simulate.rs1use super::{
2 contracts::{ISafe, ISimulateTxAccessor, SIMULATE_TX_ACCESSOR_V1_4_1},
3 deploy::ensure_contract,
4 rpc_provider,
5 service::{SafeServiceOpts, SafeTransaction},
6};
7use alloy_network::TransactionBuilder;
8use alloy_primitives::{Address, B256, Bytes, U256, hex};
9use alloy_provider::Provider;
10use alloy_rpc_types::TransactionRequest;
11use alloy_sol_types::{SolCall, SolValue};
12use clap::Args;
13use eyre::{Context, Result, ensure};
14use foundry_cli::{json::print_json_object, opts::RpcOpts};
15use serde_json::json;
16
17#[derive(Args, Debug)]
19pub struct SimulateArgs {
20 safe: Address,
22
23 safe_tx_hash: B256,
25
26 #[arg(long, env = "ETH_FROM", value_name = "ADDRESS")]
28 from: Address,
29
30 #[arg(long, default_value_t = SIMULATE_TX_ACCESSOR_V1_4_1)]
32 accessor: Address,
33
34 #[command(flatten)]
35 service: Box<SafeServiceOpts>,
36
37 #[command(flatten)]
38 rpc: Box<RpcOpts>,
39}
40
41impl SimulateArgs {
42 pub(super) async fn run(self) -> Result<()> {
43 let Self { safe, safe_tx_hash, from, accessor, service, rpc } = self;
44 let (provider, chain_id) = rpc_provider(&rpc).await?;
45 let transaction = service.get_transaction(chain_id, "v2", safe_tx_hash).await?;
46 transaction.verify_hash(safe, &provider).await?;
47 transaction.show_transaction_summary()?;
48 ensure!(
49 SafeTransaction::number(&transaction.gas_price, "gasPrice")?.is_zero(),
50 "cannot simulate reimbursed Safe transactions (gasPrice > 0): SimulateTxAccessor does not enforce safeTxGas"
51 );
52 ensure_contract(&provider, accessor, "SimulateTxAccessor", "--accessor").await?;
53
54 let accessor_call = ISimulateTxAccessor::simulateCall {
55 to: transaction.to,
56 value: SafeTransaction::number(&transaction.value, "value")?,
57 data: transaction.data.clone(),
58 operation: transaction.operation,
59 }
60 .abi_encode();
61 let simulation_call = ISafe::simulateAndRevertCall {
62 targetContract: accessor,
63 calldataPayload: accessor_call.into(),
64 }
65 .abi_encode();
66 let request =
67 TransactionRequest::default().with_from(from).with_to(safe).with_input(simulation_call);
68 let Err(error) = provider.call(request).await else {
69 eyre::bail!("Safe simulateAndRevert unexpectedly returned successfully");
70 };
71 let revert_data = error
72 .as_error_resp()
73 .and_then(|payload| payload.as_revert_data())
74 .ok_or_else(|| eyre::eyre!("Safe simulation failed without revert data: {error}"))?;
75 let (gas_used, success, return_data) = decode_result(&revert_data)?;
76 ensure!(
77 success,
78 "Safe transaction simulation failed after {gas_used} gas; return data: {return_data}"
79 );
80 print_json_object(json!({
81 "safeTxHash": safe_tx_hash,
82 "success": true,
83 "gasUsed": gas_used.to_string(),
84 "returnData": return_data,
85 }))
86 }
87}
88
89fn decode_result(revert_data: &[u8]) -> Result<(U256, bool, Bytes)> {
90 let header_length = 2 * U256::BYTES;
91 ensure!(revert_data.len() >= header_length, "invalid Safe simulateAndRevert response");
92 let accessor_success = !U256::from_be_slice(&revert_data[..U256::BYTES]).is_zero();
93 let response_len = U256::from_be_slice(&revert_data[U256::BYTES..header_length]);
94 let available = revert_data.len() - header_length;
95 ensure!(
96 response_len <= U256::from(available),
97 "invalid Safe simulateAndRevert response length"
98 );
99 let response = &revert_data[header_length..header_length + response_len.to::<usize>()];
100 ensure!(
101 accessor_success,
102 "SimulateTxAccessor delegatecall failed; return data: {}",
103 hex::encode_prefixed(response)
104 );
105 <(U256, bool, Bytes)>::abi_decode_params(response)
106 .wrap_err("invalid SimulateTxAccessor response")
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[test]
114 fn decodes_signature_independent_simulation_result() {
115 let return_data = Bytes::from_static(b"result");
116 let accessor_response = (U256::from(42), true, return_data.clone()).abi_encode_params();
117 let mut revert_data = Vec::with_capacity(2 * U256::BYTES + accessor_response.len());
118 revert_data.extend_from_slice(&U256::from(1).to_be_bytes::<{ U256::BYTES }>());
119 revert_data.extend_from_slice(
120 &U256::from(accessor_response.len()).to_be_bytes::<{ U256::BYTES }>(),
121 );
122 revert_data.extend_from_slice(&accessor_response);
123
124 let decoded = decode_result(&revert_data).unwrap();
125 assert_eq!(decoded, (U256::from(42), true, return_data));
126 }
127}