Skip to main content

cast/cmd/safe/
simulate.rs

1use 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}