Skip to main content

safe_rs/types/
call.rs

1//! Call types for Safe transactions
2
3use alloy::primitives::{Address, Bytes, U256};
4use alloy::sol_types::SolCall;
5use std::future::Future;
6
7use super::Operation;
8use crate::error::Result;
9use crate::simulation::SimulationResult;
10
11/// Trait for builders that construct and execute batches of calls.
12///
13/// This trait provides a common interface for both `EoaBuilder` and `SafeBuilder`,
14/// enabling generic code to work with either builder type.
15pub trait CallBuilder: Sized {
16    /// Returns a mutable reference to the internal calls vector.
17    fn calls_mut(&mut self) -> &mut Vec<Call>;
18
19    /// Returns a reference to the internal calls vector.
20    fn calls(&self) -> &Vec<Call>;
21
22    /// Adds a typed call to the batch.
23    fn add_typed<C: SolCall + Clone>(mut self, to: Address, call: C) -> Self {
24        let typed_call = TypedCall::new(to, call);
25        self.calls_mut().push(Call::new(
26            typed_call.to(),
27            typed_call.value,
28            typed_call.data(),
29        ));
30        self
31    }
32
33    /// Adds a typed call with value to the batch.
34    fn add_typed_with_value<C: SolCall + Clone>(
35        mut self,
36        to: Address,
37        call: C,
38        value: U256,
39    ) -> Self {
40        let typed_call = TypedCall::new(to, call).with_value(value);
41        self.calls_mut().push(Call::new(
42            typed_call.to(),
43            typed_call.value,
44            typed_call.data(),
45        ));
46        self
47    }
48
49    /// Adds a raw call to the batch.
50    fn add_raw(mut self, to: Address, value: U256, data: impl Into<Bytes>) -> Self {
51        self.calls_mut().push(Call::new(to, value, data));
52        self
53    }
54
55    /// Adds a call implementing SafeCall to the batch.
56    fn add(mut self, call: impl SafeCall) -> Self {
57        self.calls_mut().push(Call {
58            to: call.to(),
59            value: call.value(),
60            data: call.data(),
61            operation: call.operation(),
62            gas_limit: None,
63        });
64        self
65    }
66
67    /// Sets a fixed gas limit for the transaction.
68    ///
69    /// For `EoaBuilder`: Sets the gas limit on the most recently added call.
70    /// For `SafeBuilder`: Sets the top-level `safe_tx_gas` for the entire transaction.
71    ///
72    /// # Panics
73    /// For `EoaBuilder`, panics if called before adding any calls to the batch.
74    fn with_gas_limit(self, gas_limit: u64) -> Self;
75
76    /// Returns the number of calls in the batch.
77    fn call_count(&self) -> usize {
78        self.calls().len()
79    }
80
81    /// Simulates all calls and stores the results.
82    ///
83    /// This method does not return an error if the simulation reverts. Instead,
84    /// the result (success or failure) is stored internally. Use `simulation_success()`
85    /// to check if the simulation succeeded before calling `execute()`.
86    ///
87    /// After simulation, you can inspect the results via `simulation_result()`
88    /// and then call `execute()` which will use the simulation gas values.
89    fn simulate(self) -> impl Future<Output = Result<Self>> + Send;
90
91    /// Returns the simulation result if simulation was performed.
92    fn simulation_result(&self) -> Option<&SimulationResult>;
93
94    /// Checks that simulation was performed and succeeded.
95    ///
96    /// Returns `Ok(self)` if simulation was performed and all calls succeeded.
97    /// Returns `Err(Error::SimulationNotPerformed)` if `simulate()` was not called.
98    /// Returns `Err(Error::SimulationReverted { reason })` if simulation failed.
99    ///
100    /// This is useful for chaining to ensure reverting transactions are not submitted:
101    /// ```ignore
102    /// builder.simulate().await?.simulation_success()?.execute().await?
103    /// ```
104    fn simulation_success(self) -> Result<Self>;
105}
106
107/// Trait for types that can be converted to a Safe call
108pub trait SafeCall {
109    /// Returns the target address
110    fn to(&self) -> Address;
111
112    /// Returns the value to send (in wei)
113    fn value(&self) -> U256;
114
115    /// Returns the calldata
116    fn data(&self) -> Bytes;
117
118    /// Returns the operation type (Call or DelegateCall)
119    fn operation(&self) -> Operation;
120}
121
122/// A raw call with explicit to, value, data, and operation
123#[derive(Debug, Clone)]
124pub struct Call {
125    /// Target address
126    pub to: Address,
127    /// Value to send
128    pub value: U256,
129    /// Calldata
130    pub data: Bytes,
131    /// Operation type
132    pub operation: Operation,
133    /// Optional fixed gas limit (bypasses estimation if set)
134    pub gas_limit: Option<u64>,
135}
136
137impl Call {
138    /// Creates a new Call with the given parameters
139    pub fn new(to: Address, value: U256, data: impl Into<Bytes>) -> Self {
140        Self {
141            to,
142            value,
143            data: data.into(),
144            operation: Operation::Call,
145            gas_limit: None,
146        }
147    }
148
149    /// Creates a new Call with zero value
150    pub fn call(to: Address, data: impl Into<Bytes>) -> Self {
151        Self::new(to, U256::ZERO, data)
152    }
153
154    /// Creates a new delegate call
155    pub fn delegate_call(to: Address, data: impl Into<Bytes>) -> Self {
156        Self {
157            to,
158            value: U256::ZERO,
159            data: data.into(),
160            operation: Operation::DelegateCall,
161            gas_limit: None,
162        }
163    }
164
165    /// Sets the operation type
166    pub fn with_operation(mut self, operation: Operation) -> Self {
167        self.operation = operation;
168        self
169    }
170
171    /// Sets the value
172    pub fn with_value(mut self, value: U256) -> Self {
173        self.value = value;
174        self
175    }
176
177    /// Sets a fixed gas limit, bypassing gas estimation
178    pub fn with_gas_limit(mut self, gas_limit: u64) -> Self {
179        self.gas_limit = Some(gas_limit);
180        self
181    }
182}
183
184impl SafeCall for Call {
185    fn to(&self) -> Address {
186        self.to
187    }
188
189    fn value(&self) -> U256 {
190        self.value
191    }
192
193    fn data(&self) -> Bytes {
194        self.data.clone()
195    }
196
197    fn operation(&self) -> Operation {
198        self.operation
199    }
200}
201
202/// A typed call wrapping a sol! macro generated call type
203#[derive(Debug, Clone)]
204pub struct TypedCall<C: SolCall> {
205    /// Target address
206    pub to: Address,
207    /// Value to send
208    pub value: U256,
209    /// The typed call data
210    pub call: C,
211    /// Operation type
212    pub operation: Operation,
213}
214
215impl<C: SolCall> TypedCall<C> {
216    /// Creates a new TypedCall
217    pub fn new(to: Address, call: C) -> Self {
218        Self {
219            to,
220            value: U256::ZERO,
221            call,
222            operation: Operation::Call,
223        }
224    }
225
226    /// Creates a new TypedCall with value
227    pub fn with_value(mut self, value: U256) -> Self {
228        self.value = value;
229        self
230    }
231
232    /// Sets the operation type
233    pub fn with_operation(mut self, operation: Operation) -> Self {
234        self.operation = operation;
235        self
236    }
237}
238
239impl<C: SolCall + Clone> SafeCall for TypedCall<C> {
240    fn to(&self) -> Address {
241        self.to
242    }
243
244    fn value(&self) -> U256 {
245        self.value
246    }
247
248    fn data(&self) -> Bytes {
249        self.call.abi_encode().into()
250    }
251
252    fn operation(&self) -> Operation {
253        self.operation
254    }
255}
256
257#[cfg(test)]
258mod tests {
259    use super::*;
260    use alloy::primitives::address;
261
262    #[test]
263    fn test_call_new() {
264        let to = address!("0x1234567890123456789012345678901234567890");
265        let value = U256::from(1000);
266        let data = Bytes::from(vec![0x01, 0x02, 0x03]);
267
268        let call = Call::new(to, value, data.clone());
269
270        assert_eq!(call.to(), to);
271        assert_eq!(call.value(), value);
272        assert_eq!(call.data(), data);
273        assert_eq!(call.operation(), Operation::Call);
274    }
275
276    #[test]
277    fn test_call_delegate() {
278        let to = address!("0x1234567890123456789012345678901234567890");
279        let data = Bytes::from(vec![0x01, 0x02, 0x03]);
280
281        let call = Call::delegate_call(to, data.clone());
282
283        assert_eq!(call.to(), to);
284        assert_eq!(call.value(), U256::ZERO);
285        assert_eq!(call.data(), data);
286        assert_eq!(call.operation(), Operation::DelegateCall);
287    }
288}