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