1use super::*;
19use alloc::vec::Vec;
20use sp_core::{H160, U256};
21
22impl From<BlockNumberOrTag> for BlockNumberOrTagOrHash {
23 fn from(b: BlockNumberOrTag) -> Self {
24 match b {
25 BlockNumberOrTag::U256(n) => BlockNumberOrTagOrHash::BlockNumber(n),
26 BlockNumberOrTag::BlockTag(t) => BlockNumberOrTagOrHash::BlockTag(t),
27 }
28 }
29}
30
31impl From<TransactionSigned> for TransactionUnsigned {
32 fn from(tx: TransactionSigned) -> Self {
33 use TransactionSigned::*;
34 match tx {
35 Transaction7702Signed(tx) => tx.transaction_7702_unsigned.into(),
36 Transaction4844Signed(tx) => tx.transaction_4844_unsigned.into(),
37 Transaction1559Signed(tx) => tx.transaction_1559_unsigned.into(),
38 Transaction2930Signed(tx) => tx.transaction_2930_unsigned.into(),
39 TransactionLegacySigned(tx) => tx.transaction_legacy_unsigned.into(),
40 }
41 }
42}
43
44impl TransactionInfo {
45 pub fn new(receipt: &ReceiptInfo, transaction_signed: TransactionSigned) -> Self {
47 Self {
48 block_hash: receipt.block_hash,
49 block_number: receipt.block_number,
50 from: receipt.from,
51 hash: receipt.transaction_hash,
52 transaction_index: receipt.transaction_index,
53 transaction_signed,
54 }
55 }
56}
57
58impl ReceiptInfo {
59 pub fn new(
61 block_hash: H256,
62 block_number: U256,
63 contract_address: Option<Address>,
64 from: Address,
65 logs: Vec<Log>,
66 to: Option<Address>,
67 effective_gas_price: U256,
68 gas_used: U256,
69 success: bool,
70 transaction_hash: H256,
71 transaction_index: U256,
72 r#type: Byte,
73 ) -> Self {
74 let logs_bloom = Self::logs_bloom(&logs);
75 ReceiptInfo {
76 block_hash,
77 block_number,
78 contract_address,
79 from,
80 logs,
81 logs_bloom,
82 to,
83 effective_gas_price,
84 gas_used,
85 status: Some(if success { U256::one() } else { U256::zero() }),
86 transaction_hash,
87 transaction_index,
88 r#type: Some(r#type),
89 ..Default::default()
90 }
91 }
92
93 pub fn is_success(&self) -> bool {
95 self.status.map_or(false, |status| status == U256::one())
96 }
97
98 fn logs_bloom(logs: &[Log]) -> Bytes256 {
100 let mut bloom = [0u8; 256];
101 for log in logs {
102 m3_2048(&mut bloom, &log.address.as_ref());
103 for topic in &log.topics {
104 m3_2048(&mut bloom, topic.as_ref());
105 }
106 }
107 bloom.into()
108 }
109}
110fn m3_2048(bloom: &mut [u8; 256], bytes: &[u8]) {
117 let hash = sp_core::keccak_256(bytes);
118 for i in [0, 2, 4] {
119 let bit = (hash[i + 1] as usize + ((hash[i] as usize) << 8)) & 0x7FF;
120 bloom[256 - 1 - bit / 8] |= 1 << (bit % 8);
121 }
122}
123
124#[test]
125fn can_deserialize_input_or_data_field_from_generic_transaction() {
126 let cases = [
127 ("with input", r#"{"input": "0x01"}"#),
128 ("with data", r#"{"data": "0x01"}"#),
129 ("with both", r#"{"data": "0x01", "input": "0x01"}"#),
130 ];
131
132 for (name, json) in cases {
133 let tx = serde_json::from_str::<GenericTransaction>(json).unwrap();
134 assert_eq!(tx.input.to_vec(), vec![1u8], "{}", name);
135 }
136
137 let err = serde_json::from_str::<GenericTransaction>(r#"{"data": "0x02", "input": "0x01"}"#)
138 .unwrap_err();
139 assert!(
140 err.to_string().starts_with(
141 "Both \"data\" and \"input\" are set and not equal. Please use \"input\" to pass transaction call data"
142 )
143 );
144}
145
146#[test]
147fn test_block_number_or_tag_or_hash_deserialization() {
148 let val: BlockNumberOrTagOrHash = serde_json::from_str("\"latest\"").unwrap();
149 assert_eq!(val, BlockTag::Latest.into());
150
151 for s in ["\"0x1a\"", r#"{ "blockNumber": "0x1a" }"#] {
152 let val: BlockNumberOrTagOrHash = serde_json::from_str(s).unwrap();
153 assert!(matches!(val, BlockNumberOrTagOrHash::BlockNumber(n) if n == 26u64.into()));
154 }
155
156 for s in [
157 "\"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"",
158 r#"{ "blockHash": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" }"#,
159 ] {
160 let val: BlockNumberOrTagOrHash = serde_json::from_str(s).unwrap();
161 assert_eq!(val, BlockNumberOrTagOrHash::BlockHash(H256([0xaau8; 32])));
162 }
163}
164
165#[test]
166fn logs_bloom_works() {
167 let receipt: ReceiptInfo = serde_json::from_str(
168 r#"
169 {
170 "blockHash": "0x835ee379aaabf4802a22a93ad8164c02bbdde2cc03d4552d5c642faf4e09d1f3",
171 "blockNumber": "0x2",
172 "contractAddress": null,
173 "cumulativeGasUsed": "0x5d92",
174 "effectiveGasPrice": "0x2dcd5c2d",
175 "from": "0xb4f1f9ecfe5a28633a27f57300bda217e99b8969",
176 "gasUsed": "0x5d92",
177 "logs": [
178 {
179 "address": "0x82bdb002b9b1f36c42df15fbdc6886abcb2ab31d",
180 "topics": [
181 "0x1585375487296ff2f0370daeec4214074a032b31af827c12622fa9a58c16c7d0",
182 "0x000000000000000000000000b4f1f9ecfe5a28633a27f57300bda217e99b8969"
183 ],
184 "data": "0x00000000000000000000000000000000000000000000000000000000000030390000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000b48656c6c6f20776f726c64000000000000000000000000000000000000000000",
185 "blockNumber": "0x2",
186 "transactionHash": "0xad0075127962bdf73d787f2944bdb5f351876f23c35e6a48c1f5b6463a100af4",
187 "transactionIndex": "0x0",
188 "blockHash": "0x835ee379aaabf4802a22a93ad8164c02bbdde2cc03d4552d5c642faf4e09d1f3",
189 "logIndex": "0x0",
190 "removed": false
191 }
192 ],
193 "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000008000000000000000000000000000000000000000000000000800000000040000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000004000000000000000800000000000000000080000000000000000000000000000000000000000000",
194 "status": "0x1",
195 "to": "0x82bdb002b9b1f36c42df15fbdc6886abcb2ab31d",
196 "transactionHash": "0xad0075127962bdf73d787f2944bdb5f351876f23c35e6a48c1f5b6463a100af4",
197 "transactionIndex": "0x0",
198 "type": "0x2"
199 }
200 "#,
201 )
202 .unwrap();
203 assert_eq!(receipt.logs_bloom, ReceiptInfo::logs_bloom(&receipt.logs));
204}
205
206impl GenericTransaction {
207 pub fn from_signed(tx: TransactionSigned, base_gas_price: U256, from: Option<H160>) -> Self {
209 Self::from_unsigned(tx.into(), base_gas_price, from)
210 }
211
212 pub fn from_unsigned(
214 tx: TransactionUnsigned,
215 base_gas_price: U256,
216 from: Option<H160>,
217 ) -> Self {
218 use TransactionUnsigned::*;
219 match tx {
220 TransactionLegacyUnsigned(tx) => GenericTransaction {
221 from,
222 r#type: Some(tx.r#type.as_byte()),
223 chain_id: tx.chain_id,
224 input: tx.input.into(),
225 nonce: Some(tx.nonce),
226 value: Some(tx.value),
227 to: tx.to,
228 gas: Some(tx.gas),
229 gas_price: Some(tx.gas_price),
230 ..Default::default()
231 },
232 Transaction4844Unsigned(tx) => GenericTransaction {
233 from,
234 r#type: Some(tx.r#type.as_byte()),
235 chain_id: Some(tx.chain_id),
236 input: tx.input.into(),
237 nonce: Some(tx.nonce),
238 value: Some(tx.value),
239 to: Some(tx.to),
240 gas: Some(tx.gas),
241 gas_price: Some(
242 base_gas_price
243 .saturating_add(tx.max_priority_fee_per_gas)
244 .min(tx.max_fee_per_blob_gas),
245 ),
246 access_list: Some(tx.access_list),
247 blob_versioned_hashes: tx.blob_versioned_hashes,
248 max_fee_per_blob_gas: Some(tx.max_fee_per_blob_gas),
249 max_fee_per_gas: Some(tx.max_fee_per_gas),
250 max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas),
251 ..Default::default()
252 },
253 Transaction1559Unsigned(tx) => GenericTransaction {
254 from,
255 r#type: Some(tx.r#type.as_byte()),
256 chain_id: Some(tx.chain_id),
257 input: tx.input.into(),
258 nonce: Some(tx.nonce),
259 value: Some(tx.value),
260 to: tx.to,
261 gas: Some(tx.gas),
262 gas_price: Some(
263 base_gas_price
264 .saturating_add(tx.max_priority_fee_per_gas)
265 .min(tx.max_fee_per_gas),
266 ),
267 access_list: Some(tx.access_list),
268 max_fee_per_gas: Some(tx.max_fee_per_gas),
269 max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas),
270 ..Default::default()
271 },
272 Transaction2930Unsigned(tx) => GenericTransaction {
273 from,
274 r#type: Some(tx.r#type.as_byte()),
275 chain_id: Some(tx.chain_id),
276 input: tx.input.into(),
277 nonce: Some(tx.nonce),
278 value: Some(tx.value),
279 to: tx.to,
280 gas: Some(tx.gas),
281 gas_price: Some(tx.gas_price),
282 access_list: Some(tx.access_list),
283 ..Default::default()
284 },
285 Transaction7702Unsigned(tx) => GenericTransaction {
286 from,
287 r#type: Some(tx.r#type.as_byte()),
288 chain_id: Some(tx.chain_id),
289 input: tx.input.into(),
290 nonce: Some(tx.nonce),
291 value: Some(tx.value),
292 to: tx.to,
293 gas: Some(tx.gas),
294 gas_price: Some(
295 base_gas_price
296 .saturating_add(tx.max_priority_fee_per_gas)
297 .min(tx.max_fee_per_gas),
298 ),
299 access_list: Some(tx.access_list),
300 authorization_list: tx.authorization_list,
301 max_fee_per_gas: Some(tx.max_fee_per_gas),
302 max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas),
303 ..Default::default()
304 },
305 }
306 }
307
308 pub fn try_into_unsigned(self) -> Result<TransactionUnsigned, ()> {
310 match self.r#type.unwrap_or_default().0 {
311 TYPE_LEGACY => Ok(TransactionLegacyUnsigned {
312 r#type: TypeLegacy {},
313 chain_id: self.chain_id,
314 input: self.input.to_bytes(),
315 nonce: self.nonce.unwrap_or_default(),
316 value: self.value.unwrap_or_default(),
317 to: self.to,
318 gas: self.gas.unwrap_or_default(),
319 gas_price: self.gas_price.unwrap_or_default(),
320 }
321 .into()),
322 TYPE_EIP1559 => Ok(Transaction1559Unsigned {
323 r#type: TypeEip1559 {},
324 chain_id: self.chain_id.unwrap_or_default(),
325 input: self.input.to_bytes(),
326 nonce: self.nonce.unwrap_or_default(),
327 value: self.value.unwrap_or_default(),
328 to: self.to,
329 gas: self.gas.unwrap_or_default(),
330 gas_price: self.max_fee_per_gas.unwrap_or_default(),
331 access_list: self.access_list.unwrap_or_default(),
332 max_fee_per_gas: self.max_fee_per_gas.unwrap_or_default(),
333 max_priority_fee_per_gas: self.max_priority_fee_per_gas.unwrap_or_default(),
334 }
335 .into()),
336 TYPE_EIP2930 => Ok(Transaction2930Unsigned {
337 r#type: TypeEip2930 {},
338 chain_id: self.chain_id.unwrap_or_default(),
339 input: self.input.to_bytes(),
340 nonce: self.nonce.unwrap_or_default(),
341 value: self.value.unwrap_or_default(),
342 to: self.to,
343 gas: self.gas.unwrap_or_default(),
344 gas_price: self.gas_price.unwrap_or_default(),
345 access_list: self.access_list.unwrap_or_default(),
346 }
347 .into()),
348 TYPE_EIP4844 => Ok(Transaction4844Unsigned {
349 r#type: TypeEip4844 {},
350 chain_id: self.chain_id.unwrap_or_default(),
351 input: self.input.to_bytes(),
352 nonce: self.nonce.unwrap_or_default(),
353 value: self.value.unwrap_or_default(),
354 to: self.to.unwrap_or_default(),
355 gas: self.gas.unwrap_or_default(),
356 max_fee_per_gas: self.max_fee_per_gas.unwrap_or_default(),
357 max_fee_per_blob_gas: self.max_fee_per_blob_gas.unwrap_or_default(),
358 max_priority_fee_per_gas: self.max_priority_fee_per_gas.unwrap_or_default(),
359 access_list: self.access_list.unwrap_or_default(),
360 blob_versioned_hashes: self.blob_versioned_hashes,
361 }
362 .into()),
363 TYPE_EIP7702 => Ok(Transaction7702Unsigned {
364 r#type: TypeEip7702 {},
365 chain_id: self.chain_id.unwrap_or_default(),
366 input: self.input.to_bytes(),
367 nonce: self.nonce.unwrap_or_default(),
368 value: self.value.unwrap_or_default(),
369 to: self.to,
370 gas: self.gas.unwrap_or_default(),
371 gas_price: self.max_fee_per_gas.unwrap_or_default(),
372 max_fee_per_gas: self.max_fee_per_gas.unwrap_or_default(),
373 max_priority_fee_per_gas: self.max_priority_fee_per_gas.unwrap_or_default(),
374 access_list: self.access_list.unwrap_or_default(),
375 authorization_list: self.authorization_list,
376 }
377 .into()),
378 _ => Err(()),
379 }
380 }
381}
382
383#[test]
384fn from_unsigned_works_for_legacy() {
385 let base_gas_price = U256::from(10);
386 let tx = TransactionUnsigned::from(TransactionLegacyUnsigned {
387 chain_id: Some(U256::from(1)),
388 input: Bytes::from(vec![1u8]),
389 nonce: U256::from(1),
390 value: U256::from(1),
391 to: Some(H160::zero()),
392 gas: U256::from(1),
393 gas_price: U256::from(11),
394 ..Default::default()
395 });
396
397 let generic = GenericTransaction::from_unsigned(tx.clone(), base_gas_price, None);
398 assert_eq!(generic.gas_price, Some(U256::from(11)));
399
400 let tx2 = generic.try_into_unsigned().unwrap();
401 assert_eq!(tx, tx2);
402}
403
404#[test]
405fn from_unsigned_works_for_1559() {
406 let base_gas_price = U256::from(10);
407 let tx = TransactionUnsigned::from(Transaction1559Unsigned {
408 chain_id: U256::from(1),
409 input: Bytes::from(vec![1u8]),
410 nonce: U256::from(1),
411 value: U256::from(1),
412 to: Some(H160::zero()),
413 gas: U256::from(1),
414 gas_price: U256::from(20),
415 max_fee_per_gas: U256::from(20),
416 max_priority_fee_per_gas: U256::from(1),
417 ..Default::default()
418 });
419
420 let generic = GenericTransaction::from_unsigned(tx.clone(), base_gas_price, None);
421 assert_eq!(generic.gas_price, Some(U256::from(11)));
422
423 let tx2 = generic.try_into_unsigned().unwrap();
424 assert_eq!(tx, tx2);
425}
426
427#[test]
428fn from_unsigned_works_for_7702() {
429 let base_gas_price = U256::from(10);
430 let tx = TransactionUnsigned::from(Transaction7702Unsigned {
431 chain_id: U256::from(1),
432 input: Bytes::from(vec![1u8]),
433 nonce: U256::from(1),
434 value: U256::from(1),
435 to: Some(H160::zero()),
436 gas: U256::from(1),
437 gas_price: U256::from(20),
438 max_fee_per_gas: U256::from(20),
439 max_priority_fee_per_gas: U256::from(1),
440 authorization_list: vec![AuthorizationListEntry {
441 chain_id: U256::from(1),
442 address: H160::from_low_u64_be(42),
443 nonce: U256::from(0),
444 y_parity: U256::from(1),
445 r: U256::from(1),
446 s: U256::from(2),
447 }],
448 ..Default::default()
449 });
450
451 let generic = GenericTransaction::from_unsigned(tx.clone(), base_gas_price, None);
452 assert_eq!(generic.gas_price, Some(U256::from(11)));
453
454 let tx2 = generic.try_into_unsigned().unwrap();
455 assert_eq!(tx, tx2);
456}