1use crate::constants::MAX_FR_CALLDATA_TO_ALL_ENQUEUED_CALLS;
7use crate::hash::compute_calldata_hash;
8use crate::kernel_types::PrivateKernelTailPublicInputs;
9use crate::tx::{ContractClassLogFields, TypedTx};
10use crate::types::Fr;
11use crate::Error;
12
13pub fn validate_calldata(tx: &TypedTx) -> Result<(), Error> {
20 let expected_count = tx.number_of_public_calls();
21 let actual_count = tx.public_function_calldata.len();
22
23 if actual_count != expected_count {
24 return Err(Error::InvalidData(format!(
25 "TX_ERROR_CALLDATA_COUNT_MISMATCH: expected {expected_count} calldata entries, got {actual_count}"
26 )));
27 }
28
29 let total_fields = tx.get_total_public_calldata_count();
30 if total_fields > MAX_FR_CALLDATA_TO_ALL_ENQUEUED_CALLS {
31 return Err(Error::InvalidData(format!(
32 "TX_ERROR_CALLDATA_COUNT_TOO_LARGE: total calldata fields {total_fields} exceeds max {MAX_FR_CALLDATA_TO_ALL_ENQUEUED_CALLS}"
33 )));
34 }
35
36 for (request, calldata) in tx.get_public_call_requests_with_calldata() {
38 let computed_hash = compute_calldata_hash(&calldata.values);
39 if computed_hash != request.calldata_hash {
40 return Err(Error::InvalidData(format!(
41 "TX_ERROR_INCORRECT_CALLDATA: calldata hash mismatch for call to {}",
42 request.contract_address
43 )));
44 }
45 }
46
47 Ok(())
48}
49
50pub fn validate_contract_class_logs(
56 public_inputs: &PrivateKernelTailPublicInputs,
57 log_fields: &[ContractClassLogFields],
58) -> Result<(), Error> {
59 let log_hashes = public_inputs.get_non_empty_contract_class_logs_hashes();
60
61 if log_hashes.len() != log_fields.len() {
62 return Err(Error::InvalidData(format!(
63 "TX_ERROR_CONTRACT_CLASS_LOG_COUNT: expected {} log field entries, got {}",
64 log_hashes.len(),
65 log_fields.len()
66 )));
67 }
68
69 for (i, fields) in log_fields.iter().enumerate() {
71 let expected_min_length = 1 + fields
72 .fields
73 .iter()
74 .rposition(|f| *f != Fr::zero())
75 .unwrap_or(0);
76
77 if let Some(hash_entry) = log_hashes.get(i) {
78 if (hash_entry.log_hash.length as usize) < expected_min_length {
79 return Err(Error::InvalidData(format!(
80 "TX_ERROR_CONTRACT_CLASS_LOG_LENGTH: log {} has length {} but minimum is {}",
81 i, hash_entry.log_hash.length, expected_min_length
82 )));
83 }
84 }
85 }
86
87 Ok(())
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93 use crate::kernel_types::*;
94 use crate::tx::*;
95
96 #[test]
97 fn validate_calldata_empty_tx() {
98 let tx = TypedTx {
99 tx_hash: TxHash::zero(),
100 data: PrivateKernelTailPublicInputs {
101 for_rollup: Some(PartialPrivateTailPublicInputsForRollup {
102 end: PrivateToRollupAccumulatedData::default(),
103 }),
104 ..Default::default()
105 },
106 chonk_proof: ChonkProof::default(),
107 contract_class_log_fields: vec![],
108 public_function_calldata: vec![],
109 };
110 assert!(validate_calldata(&tx).is_ok());
111 }
112
113 #[test]
114 fn validate_calldata_count_mismatch() {
115 let tx = TypedTx {
116 tx_hash: TxHash::zero(),
117 data: PrivateKernelTailPublicInputs {
118 for_public: Some(PartialPrivateTailPublicInputsForPublic {
119 non_revertible_accumulated_data: PrivateToPublicAccumulatedData {
120 public_call_requests: vec![PublicCallRequest {
121 contract_address: crate::types::AztecAddress(Fr::from(1u64)),
122 ..Default::default()
123 }],
124 ..Default::default()
125 },
126 ..Default::default()
127 }),
128 ..Default::default()
129 },
130 chonk_proof: ChonkProof::default(),
131 contract_class_log_fields: vec![],
132 public_function_calldata: vec![], };
134 let err = validate_calldata(&tx).unwrap_err();
135 assert!(err.to_string().contains("CALLDATA_COUNT_MISMATCH"));
136 }
137
138 #[test]
139 fn validate_calldata_hash_match() {
140 let calldata = vec![Fr::from(42u64), Fr::from(99u64)];
141 let hash = compute_calldata_hash(&calldata);
142
143 let tx = TypedTx {
144 tx_hash: TxHash::zero(),
145 data: PrivateKernelTailPublicInputs {
146 for_public: Some(PartialPrivateTailPublicInputsForPublic {
147 non_revertible_accumulated_data: PrivateToPublicAccumulatedData {
148 public_call_requests: vec![PublicCallRequest {
149 contract_address: crate::types::AztecAddress(Fr::from(1u64)),
150 calldata_hash: hash,
151 ..Default::default()
152 }],
153 ..Default::default()
154 },
155 ..Default::default()
156 }),
157 ..Default::default()
158 },
159 chonk_proof: ChonkProof::default(),
160 contract_class_log_fields: vec![],
161 public_function_calldata: vec![HashedValues {
162 values: calldata,
163 hash,
164 }],
165 };
166 assert!(validate_calldata(&tx).is_ok());
167 }
168
169 #[test]
170 fn validate_calldata_hash_mismatch() {
171 let calldata = vec![Fr::from(42u64)];
172 let correct_hash = compute_calldata_hash(&calldata);
173 let wrong_hash = Fr::from(999u64);
174
175 let tx = TypedTx {
176 tx_hash: TxHash::zero(),
177 data: PrivateKernelTailPublicInputs {
178 for_public: Some(PartialPrivateTailPublicInputsForPublic {
179 non_revertible_accumulated_data: PrivateToPublicAccumulatedData {
180 public_call_requests: vec![PublicCallRequest {
181 contract_address: crate::types::AztecAddress(Fr::from(1u64)),
182 calldata_hash: wrong_hash,
183 ..Default::default()
184 }],
185 ..Default::default()
186 },
187 ..Default::default()
188 }),
189 ..Default::default()
190 },
191 chonk_proof: ChonkProof::default(),
192 contract_class_log_fields: vec![],
193 public_function_calldata: vec![HashedValues {
194 values: calldata,
195 hash: correct_hash,
196 }],
197 };
198 let err = validate_calldata(&tx).unwrap_err();
199 assert!(err.to_string().contains("INCORRECT_CALLDATA"));
200 }
201
202 #[test]
203 fn validate_contract_class_logs_empty() {
204 let pi = PrivateKernelTailPublicInputs {
205 for_rollup: Some(PartialPrivateTailPublicInputsForRollup {
206 end: PrivateToRollupAccumulatedData::default(),
207 }),
208 ..Default::default()
209 };
210 assert!(validate_contract_class_logs(&pi, &[]).is_ok());
211 }
212
213 #[test]
214 fn validate_contract_class_logs_count_mismatch() {
215 let pi = PrivateKernelTailPublicInputs {
216 for_rollup: Some(PartialPrivateTailPublicInputsForRollup {
217 end: PrivateToRollupAccumulatedData {
218 contract_class_logs_hashes: vec![ScopedLogHash {
219 log_hash: LogHash {
220 value: Fr::from(1u64),
221 length: 10,
222 },
223 contract_address: crate::types::AztecAddress(Fr::from(1u64)),
224 }],
225 ..Default::default()
226 },
227 }),
228 ..Default::default()
229 };
230 let err = validate_contract_class_logs(&pi, &[]).unwrap_err();
231 assert!(err.to_string().contains("LOG_COUNT"));
232 }
233}