pod_types/ledger/
receipt.rs

1use alloy_consensus::{Eip658Value, ReceiptWithBloom};
2use alloy_primitives::{Address, Bloom, Log};
3use alloy_rpc_types::TransactionReceipt;
4use alloy_sol_types::SolValue;
5use serde::{Deserialize, Serialize};
6
7use super::log;
8use crate::{
9    cryptography::{
10        hash::{Hash, Hashable},
11        merkle_tree::{MerkleBuilder, MerkleMultiProof, MerkleProof, Merkleizable, index_prefix},
12    },
13};
14
15#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]
16#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
17pub struct Receipt {
18    pub status: bool,
19    pub actual_gas_used: u64,
20    pub max_fee_per_gas: u128,
21    pub logs: Vec<Log>,
22    pub logs_root: Hash,
23    pub tx_hash: Hash,
24    pub signer: Address,
25    pub to: Option<Address>,
26    pub contract_address: Option<Address>,
27}
28
29impl Receipt {
30    pub fn tx_hash(&self) -> Hash {
31        self.tx_hash
32    }
33
34    fn log_hashes(&self) -> Vec<Hash> {
35        self.logs.iter().map(|l| l.hash_custom()).collect()
36    }
37
38    // Generates a proof for the hash of the log at a given index.
39    pub fn generate_proof_for_log_hash(&self, log_index: usize) -> Option<MerkleProof> {
40        let log_hash = self.logs.get(log_index)?.hash_custom();
41
42        self.generate_proof(&index_prefix("log_hashes", log_index), &log_hash)
43    }
44
45    // Generates a proof for the hash of each log at the given indices.
46    pub fn generate_proofs_for_log_hashes(
47        &self,
48        log_indices: &[usize],
49    ) -> Vec<Option<MerkleProof>> {
50        log_indices
51            .iter()
52            .map(|&i| self.generate_proof_for_log_hash(i))
53            .collect()
54    }
55
56    // Generates a multi proof for all log hashes in the receipt.
57    pub fn generate_multi_proof_for_log_hashes(&self) -> (Vec<Hash>, MerkleMultiProof) {
58        self.generate_multi_proof("log_hashes", &self.log_hashes())
59            .expect("log_hashes should exist")
60    }
61
62    // Generates a multi proof for the children of the receipt log at the given index.
63    pub fn generate_multi_proof_for_log(
64        &self,
65        log_index: usize,
66    ) -> Option<(Vec<Hash>, MerkleMultiProof)> {
67        self.generate_multi_proof(&index_prefix("logs", log_index), &self.logs[log_index])
68    }
69
70    // Generates a multi proof for all log children of all receipt logs.
71    pub fn generate_multi_proof_for_logs(&self) -> (Vec<Hash>, MerkleMultiProof) {
72        self.generate_multi_proofs("logs", &self.logs)
73            .expect("logs should exist in the tree")
74    }
75}
76
77impl Merkleizable for Receipt {
78    fn append_leaves(&self, builder: &mut MerkleBuilder) {
79        builder.add_field("status", self.status.abi_encode().hash_custom());
80        builder.add_field(
81            "actual_gas_used",
82            self.actual_gas_used.abi_encode().hash_custom(),
83        );
84        builder.add_slice("logs", &self.logs);
85        // NOTE: "log_hashes" isn't part of the Receipt struct
86        builder.add_slice("log_hashes", &self.log_hashes());
87        builder.add_field("logs_root", self.logs_root.abi_encode().hash_custom());
88        builder.add_field("tx_hash", self.tx_hash);
89    }
90}
91
92impl Hashable for Receipt {
93    fn hash_custom(&self) -> Hash {
94        self.to_merkle_tree().hash_custom()
95    }
96}
97
98impl From<Receipt> for TransactionReceipt {
99    fn from(val: Receipt) -> Self {
100        TransactionReceipt {
101            inner: alloy_consensus::ReceiptEnvelope::Eip1559(ReceiptWithBloom {
102                logs_bloom: Bloom::from_iter(val.logs.iter()),
103                receipt: alloy_consensus::Receipt {
104                    status: Eip658Value::Eip658(val.status),
105                    cumulative_gas_used: val.actual_gas_used, // Gas used in the block up until this tx.
106                    logs: val
107                        .logs
108                        .into_iter()
109                        .map(|l| log::to_rpc_format(l, val.tx_hash))
110                        .collect(),
111                },
112            }),
113            transaction_hash: val.tx_hash,
114            transaction_index: Some(0),
115            block_hash: Some(Hash::default()), // Need hash for tx confirmation on Metamask
116            block_number: Some(1),             // Need number of tx confirmation on Metamask
117            gas_used: val.actual_gas_used,     // Gas used by the transaction alone.
118            effective_gas_price: val.max_fee_per_gas, // Use max_fee_per_gas for EIP-1559 transactions
119            blob_gas_used: None,                      // This is none for non EIP-4844 transactions.
120            blob_gas_price: None,                     // This is none for non EIP-4844 transactions.
121            from: val.signer,
122            to: val.to,
123            contract_address: val.contract_address, // None if the transaction is not a contract creation.
124        }
125    }
126}
127
128#[cfg(test)]
129mod test {
130    use alloy_primitives::{Address, Log, LogData, TxKind, U256};
131    use alloy_signer_local::PrivateKeySigner;
132
133    use crate::{
134        Hashable, Merkleizable, Transaction, TxSigner,
135        cryptography::merkle_tree::StandardMerkleTree,
136    };
137
138    use super::Receipt;
139
140    #[tokio::test]
141    async fn test_provable_receipt() {
142        let to: Address = "217f5658c6ecc27d439922263ad9bb8e992e0373".parse().unwrap();
143        let transaction = Transaction {
144            chain_id: 0x50d,
145            to: TxKind::Call(to),
146            nonce: 1337,
147            gas_limit: 25_000,
148            max_fee_per_gas: 20_000_000_000,
149            max_priority_fee_per_gas: 1_000_000_000,
150            value: U256::ZERO,
151            access_list: Default::default(),
152            input: Default::default(),
153        };
154
155        let log = Log {
156            address: "217f5658c6ecc27d439922263ad9bb8e992e0373".parse().unwrap(),
157            data: LogData::new_unchecked(
158                vec![
159                    "84bee513033536a8de8a8260e2674a4a3eebd61ddce74615fdeca8a1499f5efe"
160                        .parse()
161                        .unwrap(),
162                    "18ed9725cd4e356a6aa0f7b9cc48d76f8c2219bacfccdb781df3fb3e71699a50"
163                        .parse()
164                        .unwrap(),
165                ],
166                vec![1].into(),
167            ),
168        };
169
170        let signer = PrivateKeySigner::random();
171        let tx = signer.sign_tx(transaction.clone()).unwrap();
172        let logs = vec![log.clone()];
173        let logs_tree = logs.to_merkle_tree();
174        let logs_root = logs_tree.root();
175        let receipt = Receipt {
176            status: true,
177            actual_gas_used: 23_112,
178            max_fee_per_gas: transaction.max_fee_per_gas,
179            logs: logs.clone(),
180            logs_root,
181            tx_hash: tx.hash_custom(),
182            signer: tx.signer,
183            to: Some(to),
184            contract_address: None,
185        };
186
187        let receipt_tree = receipt.to_merkle_tree();
188        let receipt_root = receipt_tree.hash_custom();
189
190        let log_address_leaf =
191            StandardMerkleTree::hash_leaf("logs[0].address", log.address.hash_custom());
192        let proof = receipt_tree.generate_proof(log_address_leaf).unwrap();
193        assert!(StandardMerkleTree::verify_proof(
194            receipt_root,
195            log_address_leaf,
196            proof
197        ));
198
199        let log_data_leaf =
200            StandardMerkleTree::hash_leaf("logs[0].data.data", log.data.data.hash_custom());
201        let leaves = [log_address_leaf, log_data_leaf];
202        let proof = receipt_tree.generate_multi_proof(&leaves).unwrap();
203        assert!(StandardMerkleTree::verify_multi_proof(
204            receipt_root,
205            &leaves,
206            proof
207        ));
208
209        let (leaves, proof) = receipt.generate_multi_proof_for_log(0).unwrap();
210        assert!(StandardMerkleTree::verify_multi_proof(
211            receipt_root,
212            &leaves,
213            proof
214        ));
215
216        let (leaves, proof) = receipt.generate_multi_proof_for_logs();
217        assert!(StandardMerkleTree::verify_multi_proof(
218            receipt_root,
219            &leaves,
220            proof
221        ));
222    }
223}