use alloc::vec::Vec;
use alloy_core::rlp;
use sp_core::{keccak_256, H160, H256};
pub const BLOOM_SIZE_BYTES: usize = 256;
pub struct AccumulateReceipt {
pub encoding: Vec<u8>,
pub bloom: LogsBloom,
}
impl AccumulateReceipt {
pub const fn new() -> Self {
Self { encoding: Vec::new(), bloom: LogsBloom::new() }
}
pub fn add_log(&mut self, contract: &H160, data: &[u8], topics: &[H256]) {
self.bloom.accrue_log(contract, topics);
let mut topics_len: usize = 0;
for topic in topics {
topics_len = topics_len.saturating_add(rlp::Encodable::length(&topic.0));
}
let topics_list_header_length = topics_len + rlp::length_of_length(topics_len);
let payload_length = rlp::Encodable::length(&contract.0) +
rlp::Encodable::length(&data) +
topics_list_header_length;
let header = rlp::Header { list: true, payload_length };
header.encode(&mut self.encoding);
rlp::Encodable::encode(&contract.0, &mut self.encoding);
rlp::Header { list: true, payload_length: topics_len }.encode(&mut self.encoding);
for topic in topics {
rlp::Encodable::encode(&topic.0, &mut self.encoding);
}
rlp::Encodable::encode(&data, &mut self.encoding);
}
pub fn encoded_receipt(
encoded_logs: Vec<u8>,
bloom: LogsBloom,
status: bool,
gas: u64,
transaction_type: Vec<u8>,
) -> Vec<u8> {
let logs_length = encoded_logs.len();
let list_header_length = logs_length + rlp::length_of_length(logs_length);
let header = rlp::Header {
list: true,
payload_length: rlp::Encodable::length(&status) +
rlp::Encodable::length(&gas) +
rlp::Encodable::length(&bloom.bloom) +
list_header_length,
};
let mut encoded = transaction_type;
header.encode(&mut encoded);
rlp::Encodable::encode(&status, &mut encoded);
rlp::Encodable::encode(&gas, &mut encoded);
rlp::Encodable::encode(&bloom.bloom, &mut encoded);
let logs_header = rlp::Header { list: true, payload_length: logs_length };
logs_header.encode(&mut encoded);
encoded.extend(encoded_logs);
encoded
}
}
#[derive(Clone, Copy)]
pub struct LogsBloom {
pub bloom: [u8; BLOOM_SIZE_BYTES],
}
impl Default for LogsBloom {
fn default() -> Self {
Self::new()
}
}
impl LogsBloom {
pub const fn new() -> Self {
Self { bloom: [0u8; BLOOM_SIZE_BYTES] }
}
pub fn accrue_log(&mut self, contract: &H160, topics: &[H256]) {
Self::m3_2048(&mut self.bloom, contract.as_ref());
for topic in topics {
Self::m3_2048(&mut self.bloom, topic.as_ref());
}
}
pub fn accrue_bloom(&mut self, other: &Self) {
for i in 0..BLOOM_SIZE_BYTES {
self.bloom[i] |= other.bloom[i];
}
}
fn m3_2048(bloom: &mut [u8; 256], bytes: &[u8]) {
let hash = keccak_256(bytes);
for i in [0, 2, 4] {
let bit = (hash[i + 1] as usize + ((hash[i] as usize) << 8)) & 0x7FF;
bloom[256 - 1 - bit / 8] |= 1 << (bit % 8);
}
}
}
#[cfg(test)]
mod test {
use super::*;
use alloy_consensus::RlpEncodableReceipt;
#[test]
fn test_bloom_accrue_log() {
let mut bloom = LogsBloom::new();
let data = vec![
(H160::repeat_byte(0x01), vec![H256::repeat_byte(0x02), H256::repeat_byte(0x03)]),
(H160::repeat_byte(0x04), vec![H256::repeat_byte(0x05), H256::repeat_byte(0x06)]),
(H160::repeat_byte(0x07), vec![H256::repeat_byte(0x08), H256::repeat_byte(0x09)]),
];
for (contract, topics) in data.clone() {
bloom.accrue_log(&contract, &topics);
}
let mut alloy_bloom = alloy_core::primitives::Bloom::default();
for (contract, topics) in data {
alloy_bloom.accrue_raw_log(
contract.0.into(),
&topics.iter().map(|t| t.0.into()).collect::<Vec<_>>(),
);
}
assert_eq!(bloom.bloom, alloy_bloom.0);
}
#[test]
fn test_bloom_accrue_bloom() {
let mut bloom = LogsBloom::new();
let mut bloom2 = LogsBloom::new();
bloom.accrue_log(&H160::repeat_byte(0x01), &[H256::repeat_byte(0x02)]);
bloom2.accrue_log(&H160::repeat_byte(0x03), &[H256::repeat_byte(0x04)]);
bloom.accrue_bloom(&bloom2);
let mut alloy_bloom = alloy_core::primitives::Bloom::default();
let mut alloy_bloom2 = alloy_core::primitives::Bloom::default();
alloy_bloom
.accrue_raw_log(H160::repeat_byte(0x01).0.into(), &[H256::repeat_byte(0x02).0.into()]);
alloy_bloom2
.accrue_raw_log(H160::repeat_byte(0x03).0.into(), &[H256::repeat_byte(0x04).0.into()]);
alloy_bloom.accrue_bloom(&alloy_bloom2);
assert_eq!(bloom.bloom, alloy_bloom.0);
}
#[test]
fn test_accumulate_receipt() {
let mut receipt = AccumulateReceipt::new();
receipt.add_log(&H160::repeat_byte(0x01), &[0x01, 0x02], &[H256::repeat_byte(0x02)]);
receipt.add_log(&H160::repeat_byte(0x03), &[0x03, 0x04], &[H256::repeat_byte(0x04)]);
let encoded = AccumulateReceipt::encoded_receipt(
receipt.encoding,
receipt.bloom,
true,
21000,
vec![],
);
let alloy_receipt = alloy_consensus::Receipt {
status: true.into(),
cumulative_gas_used: 21000,
logs: vec![
alloy_core::primitives::Log::new_unchecked(
H160::repeat_byte(0x01).0.into(),
vec![H256::repeat_byte(0x02).0.into()],
vec![0x01, 0x02].into(),
),
alloy_core::primitives::Log::new_unchecked(
H160::repeat_byte(0x03).0.into(),
vec![H256::repeat_byte(0x04).0.into()],
vec![0x03, 0x04].into(),
),
],
};
let alloy_bloom = alloy_receipt.bloom_slow();
assert_eq!(receipt.bloom.bloom, alloy_bloom.0);
let mut alloy_encoded = vec![];
alloy_receipt.rlp_encode_with_bloom(&alloy_bloom, &mut alloy_encoded);
assert_eq!(alloy_encoded, encoded);
}
}