stratus/eth/primitives/
log_mined.rs

1use display_json::DebugAsJson;
2use jsonrpsee::SubscriptionMessage;
3
4use super::TransactionExecution;
5use crate::alias::AlloyLog;
6use crate::alias::AlloyLogData;
7use crate::alias::AlloyLogPrimitive;
8use crate::alias::JsonValue;
9use crate::eth::primitives::Address;
10use crate::eth::primitives::BlockNumber;
11use crate::eth::primitives::Hash;
12use crate::eth::primitives::Index;
13use crate::eth::primitives::Log;
14use crate::eth::primitives::LogTopic;
15use crate::ext::to_json_value;
16
17/// Log that was emitted by the EVM and added to a block.
18#[derive(DebugAsJson, Clone, PartialEq, Eq, fake::Dummy, serde::Serialize, serde::Deserialize)]
19pub struct LogMined {
20    /// Original log emitted by the EVM.
21    pub log: Log,
22
23    /// Hash of the transaction that emitted this log.
24    pub transaction_hash: Hash,
25
26    /// Position of the transaction that emitted this log inside the block.
27    pub transaction_index: Index,
28
29    /// Position of the log inside the block.
30    pub log_index: Index,
31
32    /// Block number where the log was mined.
33    pub block_number: BlockNumber,
34
35    /// Block hash where the log was mined.
36    pub block_hash: Hash,
37}
38
39impl LogMined {
40    /// Returns the address that emitted the log.
41    pub fn address(&self) -> Address {
42        self.log.address
43    }
44
45    /// Returns all non-empty topics in the log.
46    pub fn topics_non_empty(&self) -> Vec<LogTopic> {
47        self.log.topics_non_empty()
48    }
49
50    /// Serializes itself to JSON-RPC log format.
51    pub fn to_json_rpc_log(self) -> JsonValue {
52        let alloy_log: AlloyLog = self.into();
53        to_json_value(alloy_log)
54    }
55
56    pub fn mine_log(
57        mined_log: Log,
58        block_number: BlockNumber,
59        block_hash: Hash,
60        tx: &TransactionExecution,
61        log_index: Index,
62        transaction_index: Index,
63    ) -> Self {
64        LogMined {
65            log: mined_log,
66            transaction_hash: tx.input.hash,
67            transaction_index,
68            log_index,
69            block_number,
70            block_hash,
71        }
72    }
73}
74
75// -----------------------------------------------------------------------------
76// Conversions: Other -> Self
77// -----------------------------------------------------------------------------
78impl TryFrom<AlloyLog> for LogMined {
79    type Error = anyhow::Error;
80    fn try_from(value: AlloyLog) -> Result<Self, Self::Error> {
81        let transaction_hash = value
82            .transaction_hash
83            .ok_or_else(|| anyhow::anyhow!("log must have transaction_hash"))
84            .map(|bytes| Hash::from(bytes.0))?;
85        let transaction_index = Index::from(value.transaction_index.ok_or_else(|| anyhow::anyhow!("log must have transaction_index"))?);
86        let log_index = Index::from(value.log_index.ok_or_else(|| anyhow::anyhow!("log must have log_index"))?);
87        let block_number = BlockNumber::from(value.block_number.ok_or_else(|| anyhow::anyhow!("log must have block_number"))?);
88        let block_hash = value
89            .block_hash
90            .ok_or_else(|| anyhow::anyhow!("log must have block_hash"))
91            .map(|bytes| Hash::from(bytes.0))?;
92
93        Ok(Self {
94            transaction_hash,
95            transaction_index,
96            log_index,
97            block_number,
98            block_hash,
99            log: value.into(),
100        })
101    }
102}
103
104// -----------------------------------------------------------------------------
105// Conversions: Self -> Other
106// -----------------------------------------------------------------------------
107impl From<LogMined> for AlloyLog {
108    fn from(value: LogMined) -> Self {
109        Self {
110            inner: AlloyLogPrimitive {
111                address: value.log.address.into(),
112                // Using new_unchecked is safe because topics_non_empty() guarantees ≤ 4 topics
113                data: AlloyLogData::new_unchecked(value.topics_non_empty().into_iter().map(Into::into).collect(), value.log.data.into()),
114            },
115            block_hash: Some(value.block_hash.into()),
116            block_number: Some(value.block_number.as_u64()),
117            block_timestamp: None,
118            transaction_hash: Some(value.transaction_hash.into()),
119            transaction_index: Some(value.transaction_index.into()),
120            log_index: Some(value.log_index.into()),
121            removed: false,
122        }
123    }
124}
125
126impl TryFrom<LogMined> for SubscriptionMessage {
127    type Error = serde_json::Error;
128
129    fn try_from(value: LogMined) -> Result<Self, Self::Error> {
130        Ok(serde_json::value::RawValue::from_string(serde_json::to_string(&Into::<AlloyLog>::into(value))?)?.into())
131    }
132}