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 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 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 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 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 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 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, 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()), block_number: Some(1), gas_used: val.actual_gas_used, effective_gas_price: val.max_fee_per_gas, blob_gas_used: None, blob_gas_price: None, from: val.signer,
123 to: val.to,
124 contract_address: val.contract_address, }
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}