alloy_rpc_types_eth/
call.rs

1use crate::{request::TransactionRequest, BlockId, BlockOverrides};
2use alloc::{
3    string::{String, ToString},
4    vec::Vec,
5};
6use alloy_primitives::Bytes;
7
8/// Bundle of transactions
9#[derive(Clone, Debug, Default, PartialEq, Eq)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
11#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
12pub struct Bundle {
13    /// All transactions to execute
14    pub transactions: Vec<TransactionRequest>,
15    /// Block overrides to apply
16    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
17    pub block_override: Option<BlockOverrides>,
18}
19
20impl From<Vec<TransactionRequest>> for Bundle {
21    /// Converts a `TransactionRequest` into a `Bundle`.
22    fn from(tx_request: Vec<TransactionRequest>) -> Self {
23        Self { transactions: tx_request, block_override: None }
24    }
25}
26
27/// State context for callMany
28#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
31pub struct StateContext {
32    /// Block Number
33    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
34    pub block_number: Option<BlockId>,
35    /// Inclusive number of tx to replay in block. -1 means replay all
36    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
37    #[doc(alias = "tx_index")]
38    pub transaction_index: Option<TransactionIndex>,
39}
40
41/// CallResponse for eth_callMany
42#[derive(Clone, Debug, Default, PartialEq, Eq)]
43#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
44#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
45pub struct EthCallResponse {
46    /// eth_call output (if no error)
47    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
48    pub value: Option<Bytes>,
49    /// eth_call output (if error)
50    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
51    pub error: Option<String>,
52}
53
54impl EthCallResponse {
55    /// Returns the value if present, otherwise returns the error.
56    pub fn ensure_ok(self) -> Result<Bytes, String> {
57        match self.value {
58            Some(output) => Ok(output),
59            None => Err(self.error.unwrap_or_else(|| "Unknown error".to_string())),
60        }
61    }
62}
63
64/// Represents a transaction index where -1 means all transactions
65#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
66pub enum TransactionIndex {
67    /// -1 means all transactions
68    #[default]
69    All,
70    /// Transaction index
71    Index(usize),
72}
73
74impl TransactionIndex {
75    /// Returns true if this is the all variant
76    pub const fn is_all(&self) -> bool {
77        matches!(self, Self::All)
78    }
79
80    /// Returns true if this is the index variant
81    pub const fn is_index(&self) -> bool {
82        matches!(self, Self::Index(_))
83    }
84
85    /// Returns the index if this is the index variant
86    pub const fn index(&self) -> Option<usize> {
87        match self {
88            Self::All => None,
89            Self::Index(idx) => Some(*idx),
90        }
91    }
92}
93
94impl From<usize> for TransactionIndex {
95    fn from(index: usize) -> Self {
96        Self::Index(index)
97    }
98}
99
100#[cfg(feature = "serde")]
101impl serde::Serialize for TransactionIndex {
102    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
103    where
104        S: serde::Serializer,
105    {
106        match self {
107            Self::All => serializer.serialize_i8(-1),
108            Self::Index(idx) => idx.serialize(serializer),
109        }
110    }
111}
112
113#[cfg(feature = "serde")]
114impl<'de> serde::Deserialize<'de> for TransactionIndex {
115    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
116    where
117        D: serde::Deserializer<'de>,
118    {
119        match isize::deserialize(deserializer)? {
120            -1 => Ok(Self::All),
121            idx if idx < -1 => Err(serde::de::Error::custom(format!(
122                "Invalid transaction index, expected -1 or positive integer, got {idx}"
123            ))),
124            idx => Ok(Self::Index(idx as usize)),
125        }
126    }
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132    use crate::BlockNumberOrTag;
133    use similar_asserts::assert_eq;
134
135    #[test]
136    #[cfg(feature = "serde")]
137    fn transaction_index() {
138        let s = "-1";
139        let idx = serde_json::from_str::<TransactionIndex>(s).unwrap();
140        assert_eq!(idx, TransactionIndex::All);
141
142        let s = "5";
143        let idx = serde_json::from_str::<TransactionIndex>(s).unwrap();
144        assert_eq!(idx, TransactionIndex::Index(5));
145
146        let s = "-2";
147        let res = serde_json::from_str::<TransactionIndex>(s);
148        assert!(res.is_err());
149    }
150
151    #[test]
152    #[cfg(feature = "serde")]
153    fn serde_state_context() {
154        let s = r#"{"blockNumber":"pending"}"#;
155        let state_context = serde_json::from_str::<StateContext>(s).unwrap();
156        assert_eq!(state_context.block_number, Some(BlockId::Number(BlockNumberOrTag::Pending)));
157        let state_context = serde_json::from_str::<Option<StateContext>>(s).unwrap().unwrap();
158        assert_eq!(state_context.block_number, Some(BlockId::Number(BlockNumberOrTag::Pending)));
159    }
160
161    #[test]
162    #[cfg(feature = "serde")]
163    fn serde_bundle() {
164        let s = r#"{"transactions":[{"data":"0x70a08231000000000000000000000000000000dbc80bf780c6dc0ca16ed071b1f00cc000","to":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"}],"blockOverride":{"timestamp":1711546233}}"#;
165        let bundle = serde_json::from_str::<Bundle>(s).unwrap();
166        assert_eq!(bundle.transactions.len(), 1);
167        assert_eq!(bundle.block_override.unwrap().time.unwrap(), 1711546233);
168    }
169
170    #[test]
171    #[cfg(feature = "serde")]
172    fn full_bundle() {
173        // <https://github.com/paradigmxyz/reth/issues/7542>
174        let s = r#"{"transactions":[{"from":"0x0000000000000011110000000000000000000000","to":"0x1100000000000000000000000000000000000000","value":"0x1111111","maxFeePerGas":"0x3a35294400","maxPriorityFeePerGas":"0x3b9aca00"}]}"#;
175        let bundle = serde_json::from_str::<Bundle>(s).unwrap();
176        assert_eq!(bundle.transactions.len(), 1);
177        assert_eq!(
178            bundle.transactions[0].from,
179            Some("0x0000000000000011110000000000000000000000".parse().unwrap())
180        );
181        assert_eq!(bundle.transactions[0].value, Some("0x1111111".parse().unwrap()));
182        assert_eq!(bundle.transactions[0].max_priority_fee_per_gas, Some(1000000000));
183    }
184}