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 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 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 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 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 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 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, 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()), 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,
122 to: val.to,
123 contract_address: val.contract_address, }
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}