mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
move HackReceiptFileCodec into reth-optimism-cli (#9499)
This commit is contained in:
@ -1,344 +0,0 @@
|
||||
//! Codec for reading raw receipts from a file.
|
||||
|
||||
use alloy_rlp::{Decodable, RlpDecodable};
|
||||
use reth_primitives::{
|
||||
bytes::{Buf, BytesMut},
|
||||
Address, Bloom, Bytes, Log, Receipt, TxType, B256,
|
||||
};
|
||||
use tokio_util::codec::Decoder;
|
||||
|
||||
use crate::{file_client::FileClientError, receipt_file_client::ReceiptWithBlockNumber};
|
||||
|
||||
/// Codec for reading raw receipts from a file.
|
||||
///
|
||||
/// If using with [`FramedRead`](tokio_util::codec::FramedRead), the user should make sure the
|
||||
/// framed reader has capacity for the entire receipts file. Otherwise, the decoder will return
|
||||
/// [`InputTooShort`](alloy_rlp::Error::InputTooShort), because RLP receipts can only be
|
||||
/// decoded if the internal buffer is large enough to contain the entire receipt.
|
||||
///
|
||||
/// Without ensuring the framed reader has capacity for the entire file, a receipt is likely to
|
||||
/// fall across two read buffers, the decoder will not be able to decode the receipt, which will
|
||||
/// cause it to fail.
|
||||
///
|
||||
/// It's recommended to use [`with_capacity`](tokio_util::codec::FramedRead::with_capacity) to set
|
||||
/// the capacity of the framed reader to the size of the file.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct HackReceiptFileCodec;
|
||||
|
||||
impl Decoder for HackReceiptFileCodec {
|
||||
type Item = Option<ReceiptWithBlockNumber>;
|
||||
type Error = FileClientError;
|
||||
|
||||
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
||||
if src.is_empty() {
|
||||
return Ok(None)
|
||||
}
|
||||
|
||||
let buf_slice = &mut src.as_ref();
|
||||
let receipt = HackReceiptContainer::decode(buf_slice)
|
||||
.map_err(|err| Self::Error::Rlp(err, src.to_vec()))?
|
||||
.0;
|
||||
src.advance(src.len() - buf_slice.len());
|
||||
|
||||
Ok(Some(
|
||||
receipt.map(|receipt| receipt.try_into().map_err(FileClientError::from)).transpose()?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// See <https://github.com/testinprod-io/op-geth/pull/1>
|
||||
#[derive(Debug, PartialEq, Eq, RlpDecodable)]
|
||||
pub struct HackReceipt {
|
||||
tx_type: u8,
|
||||
post_state: Bytes,
|
||||
status: u64,
|
||||
cumulative_gas_used: u64,
|
||||
bloom: Bloom,
|
||||
/// <https://github.com/testinprod-io/op-geth/blob/29062eb0fac595eeeddd3a182a25326405c66e05/core/types/log.go#L67-L72>
|
||||
logs: Vec<Log>,
|
||||
tx_hash: B256,
|
||||
contract_address: Address,
|
||||
gas_used: u64,
|
||||
block_hash: B256,
|
||||
block_number: u64,
|
||||
transaction_index: u32,
|
||||
l1_gas_price: u64,
|
||||
l1_gas_used: u64,
|
||||
l1_fee: u64,
|
||||
fee_scalar: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, RlpDecodable)]
|
||||
#[rlp(trailing)]
|
||||
struct HackReceiptContainer(Option<HackReceipt>);
|
||||
|
||||
impl TryFrom<HackReceipt> for ReceiptWithBlockNumber {
|
||||
type Error = &'static str;
|
||||
fn try_from(exported_receipt: HackReceipt) -> Result<Self, Self::Error> {
|
||||
let HackReceipt {
|
||||
tx_type, status, cumulative_gas_used, logs, block_number: number, ..
|
||||
} = exported_receipt;
|
||||
|
||||
#[allow(clippy::needless_update)]
|
||||
let receipt = Receipt {
|
||||
tx_type: TxType::try_from(tx_type.to_be_bytes()[0])?,
|
||||
success: status != 0,
|
||||
cumulative_gas_used,
|
||||
logs,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
Ok(Self { receipt, number })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(super) mod test {
|
||||
use reth_primitives::{alloy_primitives::LogData, hex};
|
||||
|
||||
use super::*;
|
||||
|
||||
pub(crate) const HACK_RECEIPT_ENCODED_BLOCK_1: &[u8] = &hex!("f9030ff9030c8080018303183db9010000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000400000000000100000000000000200000000002000000000000001000000000000000000004000000000000000000000000000040000400000100400000000000000100000000000000000000000000000020000000000000000000000000000000000000000000000001000000000000000000000100000000000000000000000000000000000000000000000000000000000000088000000080000000000010000000000000000000000000000800008000120000000000000000000000000000000002000f90197f89b948ce8c13d816fe6daf12d6fd9e4952e1fc88850aff863a00109fc6f55cf40689f02fbaad7af7fe7bbac8a3d2186600afc7d3e10cac60271a00000000000000000000000000000000000000000000000000000000000014218a000000000000000000000000070b17c0fe982ab4a7ac17a4c25485643151a1f2da000000000000000000000000000000000000000000000000000000000618d8837f89c948ce8c13d816fe6daf12d6fd9e4952e1fc88850aff884a092e98423f8adac6e64d0608e519fd1cefb861498385c6dee70d58fc926ddc68ca000000000000000000000000000000000000000000000000000000000d0e3ebf0a00000000000000000000000000000000000000000000000000000000000014218a000000000000000000000000070b17c0fe982ab4a7ac17a4c25485643151a1f2d80f85a948ce8c13d816fe6daf12d6fd9e4952e1fc88850aff842a0fe25c73e3b9089fac37d55c4c7efcba6f04af04cebd2fc4d6d7dbb07e1e5234fa000000000000000000000000000000000000000000000007edc6ca0bb6834800080a05e77a04531c7c107af1882d76cbff9486d0a9aa53701c30888509d4f5f2b003a9400000000000000000000000000000000000000008303183da0bee7192e575af30420cae0c7776304ac196077ee72b048970549e4f08e8754530180018212c2821c2383312e35");
|
||||
|
||||
pub(crate) const HACK_RECEIPT_ENCODED_BLOCK_2: &[u8] = &hex!("f90271f9026e8080018301c60db9010000080000000200000000000000000008000000000000000000000100008000000000000000000000000000000000000000000000000000000000400000000000100000000000000000000000020000000000000000000000000000000000004000000000000000000000000000000000400000000400000000000000100000000000000000000000000000020000000000000000000000000000000000000000100000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000008400000000000000000010000000000000000020000000020000000000000000000000000000000000000000000002000f8faf89c948ce8c13d816fe6daf12d6fd9e4952e1fc88850aff884a092e98423f8adac6e64d0608e519fd1cefb861498385c6dee70d58fc926ddc68ca000000000000000000000000000000000000000000000000000000000d0ea0e40a00000000000000000000000000000000000000000000000000000000000014218a0000000000000000000000000e5e7492282fd1e3bfac337a0beccd29b15b7b24080f85a948ce8c13d816fe6daf12d6fd9e4952e1fc88850aff842a0fe25c73e3b9089fac37d55c4c7efcba6f04af04cebd2fc4d6d7dbb07e1e5234fa000000000000000000000000000000000000000000000007eda7867e0c7d4800080a0af6ed8a6864d44989adc47c84f6fe0aeb1819817505c42cde6cbbcd5e14dd3179400000000000000000000000000000000000000008301c60da045fd6ce41bb8ebb2bccdaa92dd1619e287704cb07722039901a7eba63dea1d130280018212c2821c2383312e35");
|
||||
|
||||
pub(crate) const HACK_RECEIPT_ENCODED_BLOCK_3: &[u8] = &hex!("f90271f9026e8080018301c60db9010000000000000000000000000000000000000000400000000000000000008000000000000000000000000000000000004000000000000000000000400004000000100000000000000000000000000000000000000000000000000000000000004000000000000000000000040000000000400080000400000000000000100000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000008100000000000000000000000000000000000004000000000000000000000000008000000000000000000010000000000000000000000000000400000000000000001000000000000000000000000002000f8faf89c948ce8c13d816fe6daf12d6fd9e4952e1fc88850aff884a092e98423f8adac6e64d0608e519fd1cefb861498385c6dee70d58fc926ddc68ca000000000000000000000000000000000000000000000000000000000d101e54ba00000000000000000000000000000000000000000000000000000000000014218a0000000000000000000000000fa011d8d6c26f13abe2cefed38226e401b2b8a9980f85a948ce8c13d816fe6daf12d6fd9e4952e1fc88850aff842a0fe25c73e3b9089fac37d55c4c7efcba6f04af04cebd2fc4d6d7dbb07e1e5234fa000000000000000000000000000000000000000000000007ed8842f062774800080a08fab01dcec1da547e90a77597999e9153ff788fa6451d1cc942064427bd995019400000000000000000000000000000000000000008301c60da0da4509fe0ca03202ddbe4f68692c132d689ee098433691040ece18c3a45d44c50380018212c2821c2383312e35");
|
||||
|
||||
fn hack_receipt_1() -> HackReceipt {
|
||||
let receipt = receipt_block_1();
|
||||
|
||||
HackReceipt {
|
||||
tx_type: receipt.receipt.tx_type as u8,
|
||||
post_state: Bytes::default(),
|
||||
status: receipt.receipt.success as u64,
|
||||
cumulative_gas_used: receipt.receipt.cumulative_gas_used,
|
||||
bloom: Bloom::from(hex!("00000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000400000000000100000000000000200000000002000000000000001000000000000000000004000000000000000000000000000040000400000100400000000000000100000000000000000000000000000020000000000000000000000000000000000000000000000001000000000000000000000100000000000000000000000000000000000000000000000000000000000000088000000080000000000010000000000000000000000000000800008000120000000000000000000000000000000002000")),
|
||||
logs: receipt.receipt.logs,
|
||||
tx_hash: B256::from(hex!("5e77a04531c7c107af1882d76cbff9486d0a9aa53701c30888509d4f5f2b003a")), contract_address: Address::from(hex!("0000000000000000000000000000000000000000")), gas_used: 202813,
|
||||
block_hash: B256::from(hex!("bee7192e575af30420cae0c7776304ac196077ee72b048970549e4f08e875453")),
|
||||
block_number: receipt.number,
|
||||
transaction_index: 0,
|
||||
l1_gas_price: 1,
|
||||
l1_gas_used: 4802,
|
||||
l1_fee: 7203,
|
||||
fee_scalar: String::from("1.5")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn receipt_block_1() -> ReceiptWithBlockNumber {
|
||||
let log_1 = Log {
|
||||
address: Address::from(hex!("8ce8c13d816fe6daf12d6fd9e4952e1fc88850af")),
|
||||
data: LogData::new(
|
||||
vec![
|
||||
B256::from(hex!(
|
||||
"0109fc6f55cf40689f02fbaad7af7fe7bbac8a3d2186600afc7d3e10cac60271"
|
||||
)),
|
||||
B256::from(hex!(
|
||||
"0000000000000000000000000000000000000000000000000000000000014218"
|
||||
)),
|
||||
B256::from(hex!(
|
||||
"00000000000000000000000070b17c0fe982ab4a7ac17a4c25485643151a1f2d"
|
||||
)),
|
||||
],
|
||||
Bytes::from(hex!(
|
||||
"00000000000000000000000000000000000000000000000000000000618d8837"
|
||||
)),
|
||||
)
|
||||
.unwrap(),
|
||||
};
|
||||
|
||||
let log_2 = Log {
|
||||
address: Address::from(hex!("8ce8c13d816fe6daf12d6fd9e4952e1fc88850af")),
|
||||
data: LogData::new(
|
||||
vec![
|
||||
B256::from(hex!(
|
||||
"92e98423f8adac6e64d0608e519fd1cefb861498385c6dee70d58fc926ddc68c"
|
||||
)),
|
||||
B256::from(hex!(
|
||||
"00000000000000000000000000000000000000000000000000000000d0e3ebf0"
|
||||
)),
|
||||
B256::from(hex!(
|
||||
"0000000000000000000000000000000000000000000000000000000000014218"
|
||||
)),
|
||||
B256::from(hex!(
|
||||
"00000000000000000000000070b17c0fe982ab4a7ac17a4c25485643151a1f2d"
|
||||
)),
|
||||
],
|
||||
Bytes::default(),
|
||||
)
|
||||
.unwrap(),
|
||||
};
|
||||
|
||||
let log_3 = Log {
|
||||
address: Address::from(hex!("8ce8c13d816fe6daf12d6fd9e4952e1fc88850af")),
|
||||
data: LogData::new(
|
||||
vec![
|
||||
B256::from(hex!(
|
||||
"fe25c73e3b9089fac37d55c4c7efcba6f04af04cebd2fc4d6d7dbb07e1e5234f"
|
||||
)),
|
||||
B256::from(hex!(
|
||||
"00000000000000000000000000000000000000000000007edc6ca0bb68348000"
|
||||
)),
|
||||
],
|
||||
Bytes::default(),
|
||||
)
|
||||
.unwrap(),
|
||||
};
|
||||
|
||||
let mut receipt = Receipt {
|
||||
tx_type: TxType::Legacy,
|
||||
success: true,
|
||||
cumulative_gas_used: 202813,
|
||||
..Default::default()
|
||||
};
|
||||
// #[allow(clippy::needless_update)] not recognised, ..Default::default() needed so optimism
|
||||
// feature must not be brought into scope
|
||||
receipt.logs = vec![log_1, log_2, log_3];
|
||||
|
||||
ReceiptWithBlockNumber { receipt, number: 1 }
|
||||
}
|
||||
|
||||
pub(crate) fn receipt_block_2() -> ReceiptWithBlockNumber {
|
||||
let log_1 = Log {
|
||||
address: Address::from(hex!("8ce8c13d816fe6daf12d6fd9e4952e1fc88850af")),
|
||||
data: LogData::new(
|
||||
vec![
|
||||
B256::from(hex!(
|
||||
"92e98423f8adac6e64d0608e519fd1cefb861498385c6dee70d58fc926ddc68c"
|
||||
)),
|
||||
B256::from(hex!(
|
||||
"00000000000000000000000000000000000000000000000000000000d0ea0e40"
|
||||
)),
|
||||
B256::from(hex!(
|
||||
"0000000000000000000000000000000000000000000000000000000000014218"
|
||||
)),
|
||||
B256::from(hex!(
|
||||
"000000000000000000000000e5e7492282fd1e3bfac337a0beccd29b15b7b240"
|
||||
)),
|
||||
],
|
||||
Bytes::default(),
|
||||
)
|
||||
.unwrap(),
|
||||
};
|
||||
|
||||
let log_2 = Log {
|
||||
address: Address::from(hex!("8ce8c13d816fe6daf12d6fd9e4952e1fc88850af")),
|
||||
data: LogData::new(
|
||||
vec![
|
||||
B256::from(hex!(
|
||||
"fe25c73e3b9089fac37d55c4c7efcba6f04af04cebd2fc4d6d7dbb07e1e5234f"
|
||||
)),
|
||||
B256::from(hex!(
|
||||
"00000000000000000000000000000000000000000000007eda7867e0c7d48000"
|
||||
)),
|
||||
],
|
||||
Bytes::default(),
|
||||
)
|
||||
.unwrap(),
|
||||
};
|
||||
|
||||
let mut receipt = Receipt {
|
||||
tx_type: TxType::Legacy,
|
||||
success: true,
|
||||
cumulative_gas_used: 116237,
|
||||
..Default::default()
|
||||
};
|
||||
// #[allow(clippy::needless_update)] not recognised, ..Default::default() needed so optimism
|
||||
// feature must not be brought into scope
|
||||
receipt.logs = vec![log_1, log_2];
|
||||
|
||||
ReceiptWithBlockNumber { receipt, number: 2 }
|
||||
}
|
||||
|
||||
pub(crate) fn receipt_block_3() -> ReceiptWithBlockNumber {
|
||||
let log_1 = Log {
|
||||
address: Address::from(hex!("8ce8c13d816fe6daf12d6fd9e4952e1fc88850af")),
|
||||
data: LogData::new(
|
||||
vec![
|
||||
B256::from(hex!(
|
||||
"92e98423f8adac6e64d0608e519fd1cefb861498385c6dee70d58fc926ddc68c"
|
||||
)),
|
||||
B256::from(hex!(
|
||||
"00000000000000000000000000000000000000000000000000000000d101e54b"
|
||||
)),
|
||||
B256::from(hex!(
|
||||
"0000000000000000000000000000000000000000000000000000000000014218"
|
||||
)),
|
||||
B256::from(hex!(
|
||||
"000000000000000000000000fa011d8d6c26f13abe2cefed38226e401b2b8a99"
|
||||
)),
|
||||
],
|
||||
Bytes::default(),
|
||||
)
|
||||
.unwrap(),
|
||||
};
|
||||
|
||||
let log_2 = Log {
|
||||
address: Address::from(hex!("8ce8c13d816fe6daf12d6fd9e4952e1fc88850af")),
|
||||
data: LogData::new(
|
||||
vec![
|
||||
B256::from(hex!(
|
||||
"fe25c73e3b9089fac37d55c4c7efcba6f04af04cebd2fc4d6d7dbb07e1e5234f"
|
||||
)),
|
||||
B256::from(hex!(
|
||||
"00000000000000000000000000000000000000000000007ed8842f0627748000"
|
||||
)),
|
||||
],
|
||||
Bytes::default(),
|
||||
)
|
||||
.unwrap(),
|
||||
};
|
||||
|
||||
let mut receipt = Receipt {
|
||||
tx_type: TxType::Legacy,
|
||||
success: true,
|
||||
cumulative_gas_used: 116237,
|
||||
..Default::default()
|
||||
};
|
||||
// #[allow(clippy::needless_update)] not recognised, ..Default::default() needed so optimism
|
||||
// feature must not be brought into scope
|
||||
receipt.logs = vec![log_1, log_2];
|
||||
|
||||
ReceiptWithBlockNumber { receipt, number: 3 }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_hack_receipt() {
|
||||
let receipt = hack_receipt_1();
|
||||
|
||||
let decoded = HackReceiptContainer::decode(&mut &HACK_RECEIPT_ENCODED_BLOCK_1[..])
|
||||
.unwrap()
|
||||
.0
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(receipt, decoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::needless_update)]
|
||||
fn receipts_codec() {
|
||||
// rig
|
||||
|
||||
let mut receipt_1_to_3 = HACK_RECEIPT_ENCODED_BLOCK_1.to_vec();
|
||||
receipt_1_to_3.extend_from_slice(HACK_RECEIPT_ENCODED_BLOCK_2);
|
||||
receipt_1_to_3.extend_from_slice(HACK_RECEIPT_ENCODED_BLOCK_3);
|
||||
|
||||
let encoded = &mut BytesMut::from(&receipt_1_to_3[..]);
|
||||
|
||||
let mut codec = HackReceiptFileCodec;
|
||||
|
||||
// test
|
||||
|
||||
let first_decoded_receipt = codec.decode(encoded).unwrap().unwrap().unwrap();
|
||||
|
||||
assert_eq!(receipt_block_1(), first_decoded_receipt);
|
||||
|
||||
let second_decoded_receipt = codec.decode(encoded).unwrap().unwrap().unwrap();
|
||||
|
||||
assert_eq!(receipt_block_2(), second_decoded_receipt);
|
||||
|
||||
let third_decoded_receipt = codec.decode(encoded).unwrap().unwrap().unwrap();
|
||||
|
||||
assert_eq!(receipt_block_3(), third_decoded_receipt);
|
||||
}
|
||||
}
|
||||
@ -31,14 +31,6 @@ pub mod file_client;
|
||||
///
|
||||
/// Contains [`ReceiptFileClient`](receipt_file_client::ReceiptFileClient) to read receipt data from
|
||||
/// files, efficiently buffering receipts for retrieval.
|
||||
///
|
||||
/// Currently configured to use codec [`HackReceipt`](file_codec_ovm_receipt::HackReceipt) based on
|
||||
/// export of below Bedrock data using <https://github.com/testinprod-io/op-geth/pull/1>. Codec can
|
||||
/// be replaced with regular encoding of receipts for export.
|
||||
///
|
||||
/// NOTE: receipts can be exported using regular op-geth encoding for `Receipt` type, to fit
|
||||
/// reth's needs for importing. However, this would require patching the diff in <https://github.com/testinprod-io/op-geth/pull/1> to export the `Receipt` and not `HackReceipt` type (originally
|
||||
/// made for op-erigon's import needs).
|
||||
pub mod receipt_file_client;
|
||||
|
||||
/// Module with a codec for reading and encoding block bodies in files.
|
||||
@ -46,10 +38,5 @@ pub mod receipt_file_client;
|
||||
/// Enables decoding and encoding `Block` types within file contexts.
|
||||
pub mod file_codec;
|
||||
|
||||
/// Module with a codec for reading and encoding receipts in files.
|
||||
///
|
||||
/// Enables decoding and encoding `HackReceipt` type. See <https://github.com/testinprod-io/op-geth/pull/1>.
|
||||
pub mod file_codec_ovm_receipt;
|
||||
|
||||
#[cfg(any(test, feature = "test-utils"))]
|
||||
pub mod test_utils;
|
||||
|
||||
@ -220,37 +220,360 @@ pub struct ReceiptWithBlockNumber {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use reth_primitives::hex;
|
||||
use reth_tracing::init_test_tracing;
|
||||
|
||||
use crate::file_codec_ovm_receipt::{
|
||||
test::{
|
||||
receipt_block_1 as op_mainnet_receipt_block_1,
|
||||
receipt_block_2 as op_mainnet_receipt_block_2,
|
||||
receipt_block_3 as op_mainnet_receipt_block_3,
|
||||
HACK_RECEIPT_ENCODED_BLOCK_1 as HACK_RECEIPT_ENCODED_BLOCK_1_OP_MAINNET,
|
||||
HACK_RECEIPT_ENCODED_BLOCK_2 as HACK_RECEIPT_ENCODED_BLOCK_2_OP_MAINNET,
|
||||
HACK_RECEIPT_ENCODED_BLOCK_3 as HACK_RECEIPT_ENCODED_BLOCK_3_OP_MAINNET,
|
||||
},
|
||||
HackReceiptFileCodec,
|
||||
use crate::{
|
||||
file_client::{FileClientError, FromReader},
|
||||
receipt_file_client::{ReceiptFileClient, ReceiptWithBlockNumber},
|
||||
};
|
||||
use alloy_rlp::{Decodable, RlpDecodable};
|
||||
use reth_primitives::{
|
||||
hex, Address, Buf, Bytes, BytesMut, Log, LogData, Receipt, TxType, B256,
|
||||
};
|
||||
use reth_tracing::init_test_tracing;
|
||||
use tokio_util::codec::Decoder;
|
||||
|
||||
use super::*;
|
||||
#[derive(Debug, PartialEq, Eq, RlpDecodable)]
|
||||
pub struct MockReceipt {
|
||||
tx_type: u8,
|
||||
status: u64,
|
||||
cumulative_gas_used: u64,
|
||||
logs: Vec<Log>,
|
||||
block_number: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, RlpDecodable)]
|
||||
#[rlp(trailing)]
|
||||
struct MockReceiptContainer(Option<MockReceipt>);
|
||||
|
||||
impl TryFrom<MockReceipt> for ReceiptWithBlockNumber {
|
||||
type Error = &'static str;
|
||||
fn try_from(exported_receipt: MockReceipt) -> Result<Self, Self::Error> {
|
||||
let MockReceipt { tx_type, status, cumulative_gas_used, logs, block_number: number } =
|
||||
exported_receipt;
|
||||
|
||||
#[allow(clippy::needless_update)]
|
||||
let receipt = Receipt {
|
||||
tx_type: TxType::try_from(tx_type.to_be_bytes()[0])?,
|
||||
success: status != 0,
|
||||
cumulative_gas_used,
|
||||
logs,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
Ok(Self { receipt, number })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct MockReceiptFileCodec;
|
||||
|
||||
impl Decoder for MockReceiptFileCodec {
|
||||
type Item = Option<ReceiptWithBlockNumber>;
|
||||
type Error = FileClientError;
|
||||
|
||||
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
||||
if src.is_empty() {
|
||||
return Ok(None)
|
||||
}
|
||||
|
||||
let buf_slice = &mut src.as_ref();
|
||||
let receipt = MockReceiptContainer::decode(buf_slice)
|
||||
.map_err(|err| Self::Error::Rlp(err, src.to_vec()))?
|
||||
.0;
|
||||
src.advance(src.len() - buf_slice.len());
|
||||
|
||||
Ok(Some(
|
||||
receipt
|
||||
.map(|receipt| receipt.try_into().map_err(FileClientError::from))
|
||||
.transpose()?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const MOCK_RECEIPT_ENCODED_BLOCK_1: &[u8] = &hex!("f901a4f901a1800183031843f90197f89b948ce8c13d816fe6daf12d6fd9e4952e1fc88850aef863a00109fc6f55cf40689f02fbaad7af7fe7bbac8a3d2186600afc7d3e10cac6027ba00000000000000000000000000000000000000000000000000000000000014218a000000000000000000000000070b17c0fe982ab4a7ac17a4c25485643151a1f2da000000000000000000000000000000000000000000000000000000000618d8837f89c948ce8c13d816fe6daf12d6fd9e4952e1fc88850aef884a092e98423f8adac6e64d0608e519fd1cefb861498385c6dee70d58fc926ddc68ba000000000000000000000000000000000000000000000000000000000d0e3ebf0a00000000000000000000000000000000000000000000000000000000000014218a000000000000000000000000070b17c0fe982ab4a7ac17a4c25485643151a1f2d80f85a948ce8c13d816fe6daf12d6fd9e4952e1fc88850aef842a0fe25c73e3b9089fac37d55c4c7efcba6f04af04cebd2fc4d6d7dbb07e1e5234fa000000000000000000000000000000000000000000000007edc6ca0bb683480008001");
|
||||
|
||||
pub(crate) const MOCK_RECEIPT_ENCODED_BLOCK_2: &[u8] = &hex!("f90106f9010380018301c60df8faf89c948ce8c13d816fe6daf12d6fd9e4952e1fc88850aef884a092e98423f8adac6e64d0608e519fd1cefb861498385c6dee70d58fc926ddc68da000000000000000000000000000000000000000000000000000000000d0ea0e40a00000000000000000000000000000000000000000000000000000000000014218a0000000000000000000000000e5e7492282fd1e3bfac337a0beccd29b15b7b24080f85a948ce8c13d816fe6daf12d6fd9e4952e1fc88850aef842a0fe25c73e3b9089fac37d55c4c7efcba6f04af04cebd2fc4d6d7dbb07e1e5234ea000000000000000000000000000000000000000000000007eda7867e0c7d480008002");
|
||||
|
||||
pub(crate) const MOCK_RECEIPT_ENCODED_BLOCK_3: &[u8] = &hex!("f90106f9010380018301c60df8faf89c948ce8c13d816fe6daf12d6fd9e4952e1fc88850aef884a092e98423f8adac6e64d0608e519fd1cefb861498385c6dee70d58fc926ddc68da000000000000000000000000000000000000000000000000000000000d101e54ba00000000000000000000000000000000000000000000000000000000000014218a0000000000000000000000000fa011d8d6c26f13abe2cefed38226e401b2b8a9980f85a948ce8c13d816fe6daf12d6fd9e4952e1fc88850aef842a0fe25c73e3b9089fac37d55c4c7efcba6f04af04cebd2fc4d6d7dbb07e1e5234ea000000000000000000000000000000000000000000000007ed8842f06277480008003");
|
||||
|
||||
fn mock_receipt_1() -> MockReceipt {
|
||||
let receipt = receipt_block_1();
|
||||
MockReceipt {
|
||||
tx_type: receipt.receipt.tx_type as u8,
|
||||
status: receipt.receipt.success as u64,
|
||||
|
||||
cumulative_gas_used: receipt.receipt.cumulative_gas_used,
|
||||
logs: receipt.receipt.logs,
|
||||
block_number: 1,
|
||||
}
|
||||
}
|
||||
|
||||
fn mock_receipt_2() -> MockReceipt {
|
||||
let receipt = receipt_block_2();
|
||||
MockReceipt {
|
||||
tx_type: receipt.receipt.tx_type as u8,
|
||||
status: receipt.receipt.success as u64,
|
||||
|
||||
cumulative_gas_used: receipt.receipt.cumulative_gas_used,
|
||||
logs: receipt.receipt.logs,
|
||||
block_number: 2,
|
||||
}
|
||||
}
|
||||
|
||||
fn mock_receipt_3() -> MockReceipt {
|
||||
let receipt = receipt_block_3();
|
||||
MockReceipt {
|
||||
tx_type: receipt.receipt.tx_type as u8,
|
||||
status: receipt.receipt.success as u64,
|
||||
|
||||
cumulative_gas_used: receipt.receipt.cumulative_gas_used,
|
||||
logs: receipt.receipt.logs,
|
||||
block_number: 3,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn receipt_block_1() -> ReceiptWithBlockNumber {
|
||||
let log_1 = Log {
|
||||
address: Address::from(hex!("8ce8c13d816fe6daf12d6fd9e4952e1fc88850ae")),
|
||||
data: LogData::new(
|
||||
vec![
|
||||
B256::from(hex!(
|
||||
"0109fc6f55cf40689f02fbaad7af7fe7bbac8a3d2186600afc7d3e10cac6027b"
|
||||
)),
|
||||
B256::from(hex!(
|
||||
"0000000000000000000000000000000000000000000000000000000000014218"
|
||||
)),
|
||||
B256::from(hex!(
|
||||
"00000000000000000000000070b17c0fe982ab4a7ac17a4c25485643151a1f2d"
|
||||
)),
|
||||
],
|
||||
Bytes::from(hex!(
|
||||
"00000000000000000000000000000000000000000000000000000000618d8837"
|
||||
)),
|
||||
)
|
||||
.unwrap(),
|
||||
};
|
||||
|
||||
let log_2 = Log {
|
||||
address: Address::from(hex!("8ce8c13d816fe6daf12d6fd9e4952e1fc88850ae")),
|
||||
data: LogData::new(
|
||||
vec![
|
||||
B256::from(hex!(
|
||||
"92e98423f8adac6e64d0608e519fd1cefb861498385c6dee70d58fc926ddc68b"
|
||||
)),
|
||||
B256::from(hex!(
|
||||
"00000000000000000000000000000000000000000000000000000000d0e3ebf0"
|
||||
)),
|
||||
B256::from(hex!(
|
||||
"0000000000000000000000000000000000000000000000000000000000014218"
|
||||
)),
|
||||
B256::from(hex!(
|
||||
"00000000000000000000000070b17c0fe982ab4a7ac17a4c25485643151a1f2d"
|
||||
)),
|
||||
],
|
||||
Bytes::default(),
|
||||
)
|
||||
.unwrap(),
|
||||
};
|
||||
|
||||
let log_3 = Log {
|
||||
address: Address::from(hex!("8ce8c13d816fe6daf12d6fd9e4952e1fc88850ae")),
|
||||
data: LogData::new(
|
||||
vec![
|
||||
B256::from(hex!(
|
||||
"fe25c73e3b9089fac37d55c4c7efcba6f04af04cebd2fc4d6d7dbb07e1e5234f"
|
||||
)),
|
||||
B256::from(hex!(
|
||||
"00000000000000000000000000000000000000000000007edc6ca0bb68348000"
|
||||
)),
|
||||
],
|
||||
Bytes::default(),
|
||||
)
|
||||
.unwrap(),
|
||||
};
|
||||
|
||||
let mut receipt = Receipt {
|
||||
tx_type: TxType::Legacy,
|
||||
success: true,
|
||||
cumulative_gas_used: 202819,
|
||||
..Default::default()
|
||||
};
|
||||
// #[allow(clippy::needless_update)] not recognised, ..Default::default() needed so optimism
|
||||
// feature must not be brought into scope
|
||||
receipt.logs = vec![log_1, log_2, log_3];
|
||||
|
||||
ReceiptWithBlockNumber { receipt, number: 1 }
|
||||
}
|
||||
|
||||
pub(crate) fn receipt_block_2() -> ReceiptWithBlockNumber {
|
||||
let log_1 = Log {
|
||||
address: Address::from(hex!("8ce8c13d816fe6daf12d6fd9e4952e1fc88850ae")),
|
||||
data: LogData::new(
|
||||
vec![
|
||||
B256::from(hex!(
|
||||
"92e98423f8adac6e64d0608e519fd1cefb861498385c6dee70d58fc926ddc68d"
|
||||
)),
|
||||
B256::from(hex!(
|
||||
"00000000000000000000000000000000000000000000000000000000d0ea0e40"
|
||||
)),
|
||||
B256::from(hex!(
|
||||
"0000000000000000000000000000000000000000000000000000000000014218"
|
||||
)),
|
||||
B256::from(hex!(
|
||||
"000000000000000000000000e5e7492282fd1e3bfac337a0beccd29b15b7b240"
|
||||
)),
|
||||
],
|
||||
Bytes::default(),
|
||||
)
|
||||
.unwrap(),
|
||||
};
|
||||
|
||||
let log_2 = Log {
|
||||
address: Address::from(hex!("8ce8c13d816fe6daf12d6fd9e4952e1fc88850ae")),
|
||||
data: LogData::new(
|
||||
vec![
|
||||
B256::from(hex!(
|
||||
"fe25c73e3b9089fac37d55c4c7efcba6f04af04cebd2fc4d6d7dbb07e1e5234e"
|
||||
)),
|
||||
B256::from(hex!(
|
||||
"00000000000000000000000000000000000000000000007eda7867e0c7d48000"
|
||||
)),
|
||||
],
|
||||
Bytes::default(),
|
||||
)
|
||||
.unwrap(),
|
||||
};
|
||||
|
||||
let mut receipt = Receipt {
|
||||
tx_type: TxType::Legacy,
|
||||
success: true,
|
||||
cumulative_gas_used: 116237,
|
||||
..Default::default()
|
||||
};
|
||||
// #[allow(clippy::needless_update)] not recognised, ..Default::default() needed so optimism
|
||||
// feature must not be brought into scope
|
||||
receipt.logs = vec![log_1, log_2];
|
||||
|
||||
ReceiptWithBlockNumber { receipt, number: 2 }
|
||||
}
|
||||
|
||||
pub(crate) fn receipt_block_3() -> ReceiptWithBlockNumber {
|
||||
let log_1 = Log {
|
||||
address: Address::from(hex!("8ce8c13d816fe6daf12d6fd9e4952e1fc88850ae")),
|
||||
data: LogData::new(
|
||||
vec![
|
||||
B256::from(hex!(
|
||||
"92e98423f8adac6e64d0608e519fd1cefb861498385c6dee70d58fc926ddc68d"
|
||||
)),
|
||||
B256::from(hex!(
|
||||
"00000000000000000000000000000000000000000000000000000000d101e54b"
|
||||
)),
|
||||
B256::from(hex!(
|
||||
"0000000000000000000000000000000000000000000000000000000000014218"
|
||||
)),
|
||||
B256::from(hex!(
|
||||
"000000000000000000000000fa011d8d6c26f13abe2cefed38226e401b2b8a99"
|
||||
)),
|
||||
],
|
||||
Bytes::default(),
|
||||
)
|
||||
.unwrap(),
|
||||
};
|
||||
|
||||
let log_2 = Log {
|
||||
address: Address::from(hex!("8ce8c13d816fe6daf12d6fd9e4952e1fc88850ae")),
|
||||
data: LogData::new(
|
||||
vec![
|
||||
B256::from(hex!(
|
||||
"fe25c73e3b9089fac37d55c4c7efcba6f04af04cebd2fc4d6d7dbb07e1e5234e"
|
||||
)),
|
||||
B256::from(hex!(
|
||||
"00000000000000000000000000000000000000000000007ed8842f0627748000"
|
||||
)),
|
||||
],
|
||||
Bytes::default(),
|
||||
)
|
||||
.unwrap(),
|
||||
};
|
||||
|
||||
let mut receipt = Receipt {
|
||||
tx_type: TxType::Legacy,
|
||||
success: true,
|
||||
cumulative_gas_used: 116237,
|
||||
..Default::default()
|
||||
};
|
||||
// #[allow(clippy::needless_update)] not recognised, ..Default::default() needed so optimism
|
||||
// feature must not be brought into scope
|
||||
receipt.logs = vec![log_1, log_2];
|
||||
|
||||
ReceiptWithBlockNumber { receipt, number: 3 }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_mock_receipt() {
|
||||
let receipt1 = mock_receipt_1();
|
||||
let decoded1 = MockReceiptContainer::decode(&mut &MOCK_RECEIPT_ENCODED_BLOCK_1[..])
|
||||
.unwrap()
|
||||
.0
|
||||
.unwrap();
|
||||
assert_eq!(receipt1, decoded1);
|
||||
|
||||
let receipt2 = mock_receipt_2();
|
||||
let decoded2 = MockReceiptContainer::decode(&mut &MOCK_RECEIPT_ENCODED_BLOCK_2[..])
|
||||
.unwrap()
|
||||
.0
|
||||
.unwrap();
|
||||
assert_eq!(receipt2, decoded2);
|
||||
|
||||
let receipt3 = mock_receipt_3();
|
||||
let decoded3 = MockReceiptContainer::decode(&mut &MOCK_RECEIPT_ENCODED_BLOCK_3[..])
|
||||
.unwrap()
|
||||
.0
|
||||
.unwrap();
|
||||
assert_eq!(receipt3, decoded3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::needless_update)]
|
||||
fn receipts_codec() {
|
||||
// rig
|
||||
|
||||
let mut receipt_1_to_3 = MOCK_RECEIPT_ENCODED_BLOCK_1.to_vec();
|
||||
receipt_1_to_3.extend_from_slice(MOCK_RECEIPT_ENCODED_BLOCK_2);
|
||||
receipt_1_to_3.extend_from_slice(MOCK_RECEIPT_ENCODED_BLOCK_3);
|
||||
|
||||
let encoded = &mut BytesMut::from(&receipt_1_to_3[..]);
|
||||
|
||||
let mut codec = MockReceiptFileCodec;
|
||||
|
||||
// test
|
||||
|
||||
let first_decoded_receipt = codec.decode(encoded).unwrap().unwrap().unwrap();
|
||||
|
||||
assert_eq!(receipt_block_1(), first_decoded_receipt);
|
||||
|
||||
let second_decoded_receipt = codec.decode(encoded).unwrap().unwrap().unwrap();
|
||||
|
||||
assert_eq!(receipt_block_2(), second_decoded_receipt);
|
||||
|
||||
let third_decoded_receipt = codec.decode(encoded).unwrap().unwrap().unwrap();
|
||||
|
||||
assert_eq!(receipt_block_3(), third_decoded_receipt);
|
||||
}
|
||||
|
||||
/// No receipts for genesis block
|
||||
const HACK_RECEIPT_BLOCK_NO_TRANSACTIONS: &[u8] = &hex!("c0");
|
||||
const MOCK_RECEIPT_BLOCK_NO_TRANSACTIONS: &[u8] = &hex!("c0");
|
||||
|
||||
#[tokio::test]
|
||||
async fn receipt_file_client_ovm_codec() {
|
||||
init_test_tracing();
|
||||
|
||||
// genesis block has no hack receipts
|
||||
let mut encoded_receipts = HACK_RECEIPT_BLOCK_NO_TRANSACTIONS.to_vec();
|
||||
let mut encoded_receipts = MOCK_RECEIPT_BLOCK_NO_TRANSACTIONS.to_vec();
|
||||
// one receipt each for block 1 and 2
|
||||
encoded_receipts.extend_from_slice(HACK_RECEIPT_ENCODED_BLOCK_1_OP_MAINNET);
|
||||
encoded_receipts.extend_from_slice(HACK_RECEIPT_ENCODED_BLOCK_2_OP_MAINNET);
|
||||
encoded_receipts.extend_from_slice(MOCK_RECEIPT_ENCODED_BLOCK_1);
|
||||
encoded_receipts.extend_from_slice(MOCK_RECEIPT_ENCODED_BLOCK_2);
|
||||
// no receipt for block 4
|
||||
encoded_receipts.extend_from_slice(HACK_RECEIPT_BLOCK_NO_TRANSACTIONS);
|
||||
encoded_receipts.extend_from_slice(MOCK_RECEIPT_BLOCK_NO_TRANSACTIONS);
|
||||
|
||||
let encoded_byte_len = encoded_receipts.len() as u64;
|
||||
let reader = &mut &encoded_receipts[..];
|
||||
@ -258,7 +581,7 @@ mod test {
|
||||
let (
|
||||
ReceiptFileClient { receipts, first_block, total_receipts, _marker },
|
||||
_remaining_bytes,
|
||||
) = ReceiptFileClient::<HackReceiptFileCodec>::from_reader(reader, encoded_byte_len)
|
||||
) = ReceiptFileClient::<MockReceiptFileCodec>::from_reader(reader, encoded_byte_len)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@ -266,8 +589,8 @@ mod test {
|
||||
assert_eq!(2, total_receipts);
|
||||
assert_eq!(0, first_block);
|
||||
assert!(receipts[0].is_empty());
|
||||
assert_eq!(op_mainnet_receipt_block_1().receipt, receipts[1][0].clone().unwrap());
|
||||
assert_eq!(op_mainnet_receipt_block_2().receipt, receipts[2][0].clone().unwrap());
|
||||
assert_eq!(receipt_block_1().receipt, receipts[1][0].clone().unwrap());
|
||||
assert_eq!(receipt_block_2().receipt, receipts[2][0].clone().unwrap());
|
||||
assert!(receipts[3].is_empty());
|
||||
}
|
||||
|
||||
@ -276,13 +599,13 @@ mod test {
|
||||
init_test_tracing();
|
||||
|
||||
// genesis block has no hack receipts
|
||||
let mut encoded_receipts = HACK_RECEIPT_BLOCK_NO_TRANSACTIONS.to_vec();
|
||||
let mut encoded_receipts = MOCK_RECEIPT_BLOCK_NO_TRANSACTIONS.to_vec();
|
||||
// one receipt each for block 1
|
||||
encoded_receipts.extend_from_slice(HACK_RECEIPT_ENCODED_BLOCK_1_OP_MAINNET);
|
||||
encoded_receipts.extend_from_slice(MOCK_RECEIPT_ENCODED_BLOCK_1);
|
||||
// no receipt for block 2
|
||||
encoded_receipts.extend_from_slice(HACK_RECEIPT_BLOCK_NO_TRANSACTIONS);
|
||||
encoded_receipts.extend_from_slice(MOCK_RECEIPT_BLOCK_NO_TRANSACTIONS);
|
||||
// one receipt for block 3
|
||||
encoded_receipts.extend_from_slice(HACK_RECEIPT_ENCODED_BLOCK_3_OP_MAINNET);
|
||||
encoded_receipts.extend_from_slice(MOCK_RECEIPT_ENCODED_BLOCK_3);
|
||||
|
||||
let encoded_byte_len = encoded_receipts.len() as u64;
|
||||
let reader = &mut &encoded_receipts[..];
|
||||
@ -290,7 +613,7 @@ mod test {
|
||||
let (
|
||||
ReceiptFileClient { receipts, first_block, total_receipts, _marker },
|
||||
_remaining_bytes,
|
||||
) = ReceiptFileClient::<HackReceiptFileCodec>::from_reader(reader, encoded_byte_len)
|
||||
) = ReceiptFileClient::<MockReceiptFileCodec>::from_reader(reader, encoded_byte_len)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@ -298,9 +621,9 @@ mod test {
|
||||
assert_eq!(2, total_receipts);
|
||||
assert_eq!(0, first_block);
|
||||
assert!(receipts[0].is_empty());
|
||||
assert_eq!(op_mainnet_receipt_block_1().receipt, receipts[1][0].clone().unwrap());
|
||||
assert_eq!(receipt_block_1().receipt, receipts[1][0].clone().unwrap());
|
||||
assert!(receipts[2].is_empty());
|
||||
assert_eq!(op_mainnet_receipt_block_3().receipt, receipts[3][0].clone().unwrap());
|
||||
assert_eq!(receipt_block_3().receipt, receipts[3][0].clone().unwrap());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@ -308,14 +631,14 @@ mod test {
|
||||
init_test_tracing();
|
||||
|
||||
// genesis block has no hack receipts
|
||||
let mut encoded_receipts = HACK_RECEIPT_BLOCK_NO_TRANSACTIONS.to_vec();
|
||||
let mut encoded_receipts = MOCK_RECEIPT_BLOCK_NO_TRANSACTIONS.to_vec();
|
||||
// one receipt each for block 1
|
||||
encoded_receipts.extend_from_slice(HACK_RECEIPT_ENCODED_BLOCK_1_OP_MAINNET);
|
||||
encoded_receipts.extend_from_slice(MOCK_RECEIPT_ENCODED_BLOCK_1);
|
||||
// two receipts for block 2
|
||||
encoded_receipts.extend_from_slice(HACK_RECEIPT_ENCODED_BLOCK_2_OP_MAINNET);
|
||||
encoded_receipts.extend_from_slice(HACK_RECEIPT_ENCODED_BLOCK_2_OP_MAINNET);
|
||||
encoded_receipts.extend_from_slice(MOCK_RECEIPT_ENCODED_BLOCK_2);
|
||||
encoded_receipts.extend_from_slice(MOCK_RECEIPT_ENCODED_BLOCK_2);
|
||||
// one receipt for block 3
|
||||
encoded_receipts.extend_from_slice(HACK_RECEIPT_ENCODED_BLOCK_3_OP_MAINNET);
|
||||
encoded_receipts.extend_from_slice(MOCK_RECEIPT_ENCODED_BLOCK_3);
|
||||
|
||||
let encoded_byte_len = encoded_receipts.len() as u64;
|
||||
let reader = &mut &encoded_receipts[..];
|
||||
@ -323,7 +646,7 @@ mod test {
|
||||
let (
|
||||
ReceiptFileClient { receipts, first_block, total_receipts, _marker },
|
||||
_remaining_bytes,
|
||||
) = ReceiptFileClient::<HackReceiptFileCodec>::from_reader(reader, encoded_byte_len)
|
||||
) = ReceiptFileClient::<MockReceiptFileCodec>::from_reader(reader, encoded_byte_len)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@ -331,9 +654,9 @@ mod test {
|
||||
assert_eq!(4, total_receipts);
|
||||
assert_eq!(0, first_block);
|
||||
assert!(receipts[0].is_empty());
|
||||
assert_eq!(op_mainnet_receipt_block_1().receipt, receipts[1][0].clone().unwrap());
|
||||
assert_eq!(op_mainnet_receipt_block_2().receipt, receipts[2][0].clone().unwrap());
|
||||
assert_eq!(op_mainnet_receipt_block_2().receipt, receipts[2][1].clone().unwrap());
|
||||
assert_eq!(op_mainnet_receipt_block_3().receipt, receipts[3][0].clone().unwrap());
|
||||
assert_eq!(receipt_block_1().receipt, receipts[1][0].clone().unwrap());
|
||||
assert_eq!(receipt_block_2().receipt, receipts[2][0].clone().unwrap());
|
||||
assert_eq!(receipt_block_2().receipt, receipts[2][1].clone().unwrap());
|
||||
assert_eq!(receipt_block_3().receipt, receipts[3][0].clone().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user