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