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