Skip to main content

snarkvm_circuit_program/response/
process_outputs_from_callback.rs

1// Copyright (c) 2019-2026 Provable Inc.
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use super::*;
17
18impl<A: Aleo> Response<A> {
19    /// Returns the injected circuit outputs, given the number of inputs, tvk, tcm, outputs, output types, and output registers.
20    pub fn process_outputs_from_callback(
21        network_id: &U16<A>,
22        program_id: &ProgramID<A>,
23        function_name: &Identifier<A>,
24        num_inputs: usize,
25        tvk: &Field<A>,
26        tcm: &Field<A>,
27        outputs: Vec<console::Value<A::Network>>,        // Note: Console type
28        output_types: &[console::ValueType<A::Network>], // Note: Console type
29        output_registers: &[Option<console::Register<A::Network>>], // Note: Console type
30        function_id: Option<Field<A>>,
31    ) -> Vec<Value<A>> {
32        // Compute the function ID.
33        let function_id = match function_id {
34            Some(function_id) => function_id,
35            None => compute_function_id(network_id, program_id, function_name),
36        };
37
38        if outputs.len() != output_types.len() || output_types.len() != output_registers.len() {
39            let msg = format!(
40                "Mismatch in the number of outputs, output types, and output registers: {} vs {} vs {}",
41                outputs.len(),
42                output_types.len(),
43                output_registers.len()
44            );
45            return A::halt(msg);
46        }
47
48        match outputs
49            .iter()
50            .zip_eq(output_types)
51            .zip_eq(output_registers)
52            .enumerate()
53            .map(|(index, ((output, output_types), output_register))| {
54                match output_types {
55                    // For a constant output, compute the hash (using `tcm`) of the output.
56                    console::ValueType::Constant(..) => {
57                        // Inject the output as `Mode::Constant`.
58                        let output = Value::new(Mode::Constant, output.clone());
59                        // Ensure the output is a plaintext.
60                        ensure!(matches!(output, Value::Plaintext(..)), "Expected a plaintext output");
61
62                        // Prepare the index as a constant field element.
63                        let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
64                        // Construct the preimage as `(function ID || output || tcm || index)`.
65                        let mut preimage = Vec::new();
66                        preimage.push(function_id.clone());
67                        preimage.extend(output.to_fields());
68                        preimage.push(tcm.clone());
69                        preimage.push(output_index);
70
71                        // Hash the output to a field element.
72                        match &output {
73                            // Return the output ID.
74                            Value::Plaintext(..) => Ok((OutputID::constant(A::hash_psd8(&preimage)), output)),
75                            // Ensure the output is a plaintext.
76                            Value::Record(..) => A::halt("Expected a plaintext output, found a record output"),
77                            Value::Future(..) => A::halt("Expected a plaintext output, found a future output"),
78                            Value::DynamicRecord(..) => {
79                                A::halt("Expected a plaintext output, found a dynamic record output")
80                            }
81                            Value::DynamicFuture(..) => {
82                                A::halt("Expected a plaintext output, found a dynamic future output")
83                            }
84                        }
85                    }
86                    // For a public output, compute the hash (using `tcm`) of the output.
87                    console::ValueType::Public(..) => {
88                        // Inject the output as `Mode::Private`.
89                        let output = Value::new(Mode::Private, output.clone());
90                        // Ensure the output is a plaintext.
91                        ensure!(matches!(output, Value::Plaintext(..)), "Expected a plaintext output");
92
93                        // Prepare the index as a constant field element.
94                        let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
95                        // Construct the preimage as `(function ID || output || tcm || index)`.
96                        let mut preimage = Vec::new();
97                        preimage.push(function_id.clone());
98                        preimage.extend(output.to_fields());
99                        preimage.push(tcm.clone());
100                        preimage.push(output_index);
101
102                        // Hash the output to a field element.
103                        match &output {
104                            // Return the output ID.
105                            Value::Plaintext(..) => Ok((OutputID::public(A::hash_psd8(&preimage)), output)),
106                            // Ensure the output is a plaintext.
107                            Value::Record(..) => A::halt("Expected a plaintext output, found a record output"),
108                            Value::Future(..) => A::halt("Expected a plaintext output, found a future output"),
109                            Value::DynamicRecord(..) => {
110                                A::halt("Expected a plaintext output, found a dynamic record output")
111                            }
112                            Value::DynamicFuture(..) => {
113                                A::halt("Expected a plaintext output, found a dynamic future output")
114                            }
115                        }
116                    }
117                    // For a private output, compute the ciphertext (using `tvk`) and hash the ciphertext.
118                    console::ValueType::Private(..) => {
119                        // Inject the output as `Mode::Private`.
120                        let output = Value::new(Mode::Private, output.clone());
121                        // Ensure the output is a plaintext.
122                        ensure!(matches!(output, Value::Plaintext(..)), "Expected a plaintext output");
123
124                        // Prepare the index as a constant field element.
125                        let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
126                        // Compute the output view key as `Hash(function ID || tvk || index)`.
127                        let output_view_key = A::hash_psd4(&[function_id.clone(), tvk.clone(), output_index]);
128                        // Compute the ciphertext.
129                        let ciphertext = match &output {
130                            Value::Plaintext(plaintext) => plaintext.encrypt_symmetric(output_view_key),
131                            // Ensure the output is a plaintext.
132                            Value::Record(..) => A::halt("Expected a plaintext output, found a record output"),
133                            Value::Future(..) => A::halt("Expected a plaintext output, found a future output"),
134                            Value::DynamicRecord(..) => {
135                                A::halt("Expected a plaintext output, found a dynamic record output")
136                            }
137                            Value::DynamicFuture(..) => {
138                                A::halt("Expected a plaintext output, found a dynamic future output")
139                            }
140                        };
141                        // Return the output ID.
142                        Ok((OutputID::private(A::hash_psd8(&ciphertext.to_fields())), output))
143                    }
144                    // For a record output, compute the record commitment.
145                    console::ValueType::Record(record_name) => {
146                        // Inject the output as `Mode::Private`.
147                        let output = Value::new(Mode::Private, output.clone());
148
149                        // Retrieve the record.
150                        let record = match &output {
151                            Value::Record(record) => record,
152                            // Ensure the output is a record.
153                            Value::Plaintext(..) => A::halt("Expected a record output, found a plaintext output"),
154                            Value::Future(..) => A::halt("Expected a record output, found a future output"),
155                            Value::DynamicRecord(..) => {
156                                A::halt("Expected a record output, found a dynamic record output")
157                            }
158                            Value::DynamicFuture(..) => {
159                                A::halt("Expected a record output, found a dynamic future output")
160                            }
161                        };
162
163                        // Retrieve the output register.
164                        let output_register = match output_register {
165                            Some(output_register) => output_register,
166                            None => A::halt("Expected a register to be paired with a record output"),
167                        };
168
169                        // Prepare the index as a constant field element.
170                        let output_index = Field::constant(console::Field::from_u64(output_register.locator()));
171
172                        // Compute the encryption randomizer as `HashToScalar(tvk || index)`.
173                        let randomizer = A::hash_to_scalar_psd2(&[tvk.clone(), output_index]);
174
175                        // Compute the record view key.
176                        let record_view_key = ((*record.owner()).to_group() * randomizer).to_x_coordinate();
177
178                        // Compute the record commitment.
179                        let commitment =
180                            record.to_commitment(program_id, &Identifier::constant(*record_name), &record_view_key);
181
182                        // Return the output ID.
183                        // Note: Because this is a callback, the output ID is an **external record** ID.
184                        Ok((OutputID::external_record(commitment), output))
185                    }
186                    // For an external record output, compute the hash (using `tvk`) of the output.
187                    console::ValueType::ExternalRecord(..) => {
188                        // Inject the output as `Mode::Private`.
189                        let output = Value::new(Mode::Private, output.clone());
190                        // Ensure the output is a record.
191                        ensure!(matches!(output, Value::Record(..)), "Expected a record output");
192
193                        // Prepare the index as a constant field element.
194                        let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
195                        // Construct the preimage as `(function ID || output || tvk || index)`.
196                        let mut preimage = Vec::new();
197                        preimage.push(function_id.clone());
198                        preimage.extend(output.to_fields());
199                        preimage.push(tvk.clone());
200                        preimage.push(output_index);
201
202                        // Return the output ID.
203                        match &output {
204                            Value::Record(..) => Ok((OutputID::external_record(A::hash_psd8(&preimage)), output)),
205                            // Ensure the output is a record.
206                            Value::Plaintext(..) => A::halt("Expected a record output, found a plaintext output"),
207                            Value::Future(..) => A::halt("Expected a record output, found a future output"),
208                            Value::DynamicRecord(..) => {
209                                A::halt("Expected a record output, found a dynamic record output")
210                            }
211                            Value::DynamicFuture(..) => {
212                                A::halt("Expected a record output, found a dynamic future output")
213                            }
214                        }
215                    }
216                    // For a future output, compute the hash (using `tcm`) of the output.
217                    console::ValueType::Future(..) => {
218                        // Inject the output as `Mode::Private`.
219                        let output = Value::new(Mode::Private, output.clone());
220                        // Ensure the output is a future.
221                        ensure!(matches!(output, Value::Future(..)), "Expected a future output");
222
223                        // Prepare the index as a constant field element.
224                        let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
225                        // Construct the preimage as `(function ID || output || tcm || index)`.
226                        let mut preimage = Vec::new();
227                        preimage.push(function_id.clone());
228                        preimage.extend(output.to_fields());
229                        preimage.push(tcm.clone());
230                        preimage.push(output_index);
231
232                        // Hash the output to a field element.
233                        match &output {
234                            // Return the output ID.
235                            Value::Future(..) => Ok((OutputID::future(A::hash_psd8(&preimage)), output)),
236                            // Ensure the output is a future.
237                            Value::Plaintext(..) => A::halt("Expected a future output, found a plaintext output"),
238                            Value::Record(..) => A::halt("Expected a future output, found a record output"),
239                            Value::DynamicRecord(..) => {
240                                A::halt("Expected a future output, found a dynamic record output")
241                            }
242                            Value::DynamicFuture(..) => {
243                                A::halt("Expected a future output, found a dynamic future output")
244                            }
245                        }
246                    }
247                    // For a dynamic record output, compute the hash (using `tvk`) of the output.
248                    console::ValueType::DynamicRecord => {
249                        // Inject the output as `Mode::Private`.
250                        let output = Value::new(Mode::Private, output.clone());
251                        // Ensure the output is a dynamic record.
252                        ensure!(matches!(output, Value::DynamicRecord(..)), "Expected a dynamic record output");
253
254                        // Prepare the index as a constant field element.
255                        let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
256                        // Construct the preimage as `(function ID || output || tvk || index)`.
257                        let mut preimage = Vec::new();
258                        preimage.push(function_id.clone());
259                        preimage.extend(output.to_fields());
260                        preimage.push(tvk.clone());
261                        preimage.push(output_index);
262
263                        // Return the output ID.
264                        match &output {
265                            Value::DynamicRecord(..) => Ok((OutputID::dynamic_record(A::hash_psd8(&preimage)), output)),
266                            // Ensure the output is a dynamic record.
267                            Value::Plaintext(..) => {
268                                A::halt("Expected a dynamic record output, found a plaintext output")
269                            }
270                            Value::Future(..) => A::halt("Expected a dynamic record output, found a future output"),
271                            Value::Record(..) => A::halt("Expected a dynamic record output, found a record output"),
272                            Value::DynamicFuture(..) => {
273                                A::halt("Expected a dynamic record output, found a dynamic future output")
274                            }
275                        }
276                    }
277                    // For a dynamic future output, compute the hash (using `tcm`) of the output.
278                    console::ValueType::DynamicFuture => {
279                        // Inject the output as `Mode::Private`.
280                        let output = Value::new(Mode::Private, output.clone());
281                        // Ensure the output is a dynamic future.
282                        ensure!(matches!(output, Value::DynamicFuture(..)), "Expected a dynamic future output");
283
284                        // Prepare the index as a constant field element.
285                        let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
286                        // Construct the preimage as `(function ID || output || tcm || index)`.
287                        let mut preimage = Vec::new();
288                        preimage.push(function_id.clone());
289                        preimage.extend(output.to_fields());
290                        preimage.push(tcm.clone());
291                        preimage.push(output_index);
292
293                        // Hash the output to a field element.
294                        match &output {
295                            // Return the output ID.
296                            Value::DynamicFuture(..) => Ok((OutputID::dynamic_future(A::hash_psd8(&preimage)), output)),
297                            // Ensure the output is a dynamic future.
298                            Value::Plaintext(..) => {
299                                A::halt("Expected a dynamic future output, found a plaintext output")
300                            }
301                            Value::Record(..) => A::halt("Expected a dynamic future output, found a record output"),
302                            Value::DynamicRecord(..) => {
303                                A::halt("Expected a dynamic future output, found a dynamic record output")
304                            }
305                            Value::Future(..) => A::halt("Expected a dynamic future output, found a future output"),
306                        }
307                    }
308                }
309            })
310            .collect::<Result<Vec<_>>>()
311        {
312            Ok(outputs) => {
313                // Unzip the output IDs from the output values.
314                let (_, outputs): (Vec<OutputID<A>>, _) = outputs.into_iter().unzip();
315                // Return the outputs.
316                outputs
317            }
318            Err(error) => A::halt(error.to_string()),
319        }
320    }
321}
322
323#[cfg(test)]
324mod tests {
325    use super::*;
326    use crate::Circuit;
327    use snarkvm_circuit_types::environment::UpdatableCount;
328    use snarkvm_utilities::{TestRng, Uniform};
329
330    use anyhow::Result;
331
332    pub(crate) const ITERATIONS: usize = 10;
333
334    fn check_from_callback(
335        mode: Mode,
336        program_id: &str,
337        function_name: &str,
338        is_dynamic: bool,
339        use_record: bool,
340        expected_count: UpdatableCount,
341    ) -> Result<()> {
342        use console::Network;
343
344        let rng = &mut TestRng::default();
345
346        for i in 0..ITERATIONS {
347            // Sample a `tvk`.
348            let tvk = console::Field::rand(rng);
349            // Compute the transition commitment as `Hash(tvk)`.
350            let tcm = <Circuit as Environment>::Network::hash_psd2(&[tvk])?;
351
352            // Compute the nonce.
353            let index = console::Field::from_u64(8);
354            let randomizer = <Circuit as Environment>::Network::hash_to_scalar_psd2(&[tvk, index]).unwrap();
355            let nonce = <Circuit as Environment>::Network::g_scalar_multiply(&randomizer);
356
357            // Construct the outputs.
358            let output_constant = console::Value::<<Circuit as Environment>::Network>::Plaintext(
359                console::Plaintext::from_str("{ token_amount: 9876543210u128 }").unwrap(),
360            );
361            let output_public = console::Value::<<Circuit as Environment>::Network>::Plaintext(
362                console::Plaintext::from_str("{ token_amount: 9876543210u128 }").unwrap(),
363            );
364            let output_private = console::Value::<<Circuit as Environment>::Network>::Plaintext(
365                console::Plaintext::from_str("{ token_amount: 9876543210u128 }").unwrap(),
366            );
367            let output_record = console::Value::<<Circuit as Environment>::Network>::Record(console::Record::from_str(&format!("{{ owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.private, token_amount: 100u64.private, _nonce: {nonce}.public }}")).unwrap());
368            let output_external_record = console::Value::<<Circuit as Environment>::Network>::Record(console::Record::from_str("{ owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.private, token_amount: 100u64.private, _nonce: 0group.public }").unwrap());
369            let outputs = if use_record {
370                vec![output_constant, output_public, output_private, output_record, output_external_record]
371            } else {
372                vec![output_constant, output_public, output_private, output_external_record]
373            };
374
375            // Construct the output types.
376            let output_types = if use_record {
377                vec![
378                    console::ValueType::from_str("amount.constant").unwrap(),
379                    console::ValueType::from_str("amount.public").unwrap(),
380                    console::ValueType::from_str("amount.private").unwrap(),
381                    console::ValueType::from_str("token.record").unwrap(),
382                    console::ValueType::from_str("token.aleo/token.record").unwrap(),
383                ]
384            } else {
385                vec![
386                    console::ValueType::from_str("amount.constant").unwrap(),
387                    console::ValueType::from_str("amount.public").unwrap(),
388                    console::ValueType::from_str("amount.private").unwrap(),
389                    console::ValueType::from_str("token.aleo/token.record").unwrap(),
390                ]
391            };
392
393            // Construct the output registers.
394            let mut output_registers = vec![
395                Some(console::Register::Locator(5)),
396                Some(console::Register::Locator(6)),
397                Some(console::Register::Locator(7)),
398                Some(console::Register::Locator(8)),
399            ];
400            if use_record {
401                output_registers.push(Some(console::Register::Locator(9)));
402            }
403
404            // Construct a signer.
405            let signer = console::Address::rand(rng);
406            // Construct a network ID.
407            let network_id = console::U16::new(<Circuit as Environment>::Network::ID);
408            // Construct a program ID.
409            let program_id = console::ProgramID::from_str(program_id)?;
410            // Construct a function name.
411            let function_name = console::Identifier::from_str(function_name)?;
412
413            // Construct the response.
414            let response = console::Response::new(
415                &signer,
416                &network_id,
417                &program_id,
418                &function_name,
419                4,
420                &tvk,
421                &tcm,
422                outputs.clone(),
423                &output_types,
424                &output_registers,
425            )?;
426
427            // Inject the signer, network ID, program ID, function name, `tvk`, `tcm`.
428            let signer = Address::<Circuit>::new(mode, signer);
429            let network_id = U16::<Circuit>::constant(network_id);
430            let program_id = match is_dynamic {
431                false => ProgramID::<Circuit>::constant(program_id),
432                true => ProgramID::<Circuit>::public(program_id),
433            };
434            let function_name = match is_dynamic {
435                false => Identifier::<Circuit>::constant(function_name),
436                true => Identifier::<Circuit>::public(function_name),
437            };
438            let tvk = Field::<Circuit>::new(mode, tvk);
439            let tcm = Field::<Circuit>::new(mode, tcm);
440            let function_id =
441                if is_dynamic { Some(compute_function_id(&network_id, &program_id, &function_name)) } else { None };
442
443            Circuit::scope(format!("Response {i}"), || {
444                let outputs = Response::process_outputs_from_callback(
445                    &network_id,
446                    &program_id,
447                    &function_name,
448                    4,
449                    &tvk,
450                    &tcm,
451                    response.outputs().to_vec(),
452                    &output_types,
453                    &output_registers,
454                    function_id,
455                );
456                assert_eq!(response.outputs(), outputs.eject_value());
457                expected_count.assert_matches(
458                    Circuit::num_constants_in_scope(),
459                    Circuit::num_public_in_scope(),
460                    Circuit::num_private_in_scope(),
461                    Circuit::num_constraints_in_scope(),
462                );
463            });
464
465            // Compute the response using outputs (circuit).
466            let outputs = Inject::new(mode, response.outputs().to_vec());
467            let candidate_b = Response::from_outputs(
468                &signer,
469                &network_id,
470                &program_id,
471                &function_name,
472                4,
473                &tvk,
474                &tcm,
475                outputs,
476                &output_types,
477                &output_registers,
478            );
479            assert_eq!(response, candidate_b.eject_value());
480
481            Circuit::reset();
482        }
483        Ok(())
484    }
485
486    // Note: These counts are correct. At this (high) level of a program, we override the default mode in many cases,
487    // based on the user-defined visibility in the types. Thus, we have nonzero public, private, and constraint values.
488    // These bounds are determined experimentally.
489
490    #[test]
491    #[rustfmt::skip]
492    fn test_from_callback_constant() -> Result<()> {
493        // Static response without records.
494        check_from_callback(Mode::Constant, "test.aleo", "foo", false, false, count_less_than!(19917, 4, 2813, 2819))?;
495        check_from_callback(Mode::Constant, "credits.aleo", "transfer_public", false, false, count_less_than!(1531, 4, 2813, 2819))?; 
496
497        // Static response with records.
498        check_from_callback(Mode::Constant, "test.aleo", "foo", false, true, count_less_than!(16062, 5, 10910, 10923))?;
499        check_from_callback(Mode::Constant, "credits.aleo", "transfer_public", false, true, count_less_than!(4303, 5, 11012, 11025))?;
500
501
502        // Dynamic response without records.
503        check_from_callback(Mode::Constant, "test.aleo", "foo", true, false, count_less_than!(1233, 4, 6937, 6949))?;
504        check_from_callback(Mode::Constant, "credits.aleo", "transfer_public", true, false, count_less_than!(1233, 4, 6937, 6949))?;
505
506        // Dynamic response with records.
507        check_from_callback(Mode::Constant, "test.aleo", "foo", true, true, count_less_than!(3975, 5, 16167, 16190))?;
508        check_from_callback(Mode::Constant, "credits.aleo", "transfer_public", true, true, count_less_than!(3843, 5, 16339, 16362))?;
509
510        Ok(())
511    }
512
513    #[test]
514    #[rustfmt::skip]
515    fn test_from_callback_public() -> Result<()> {
516        // Static response without records.
517        check_from_callback(Mode::Public, "test.aleo", "foo", false, false, count_is!(<=19917, 4, 4108, 4114))?;
518        check_from_callback(Mode::Public, "credits.aleo", "transfer_public", false, false, count_is!(1529, 4, 4108, 4114))?;
519
520        // Static response with records.
521        check_from_callback(Mode::Public, "test.aleo", "foo", false, true, count_is!(<=15809, 5, 13475, 13490))?;
522        check_from_callback(Mode::Public, "credits.aleo", "transfer_public", false, true, count_is!(4046, 5, 13577, 13592))?;
523
524        // Dynamic response without records.
525        check_from_callback(Mode::Public, "test.aleo", "foo", true, false, count_is!(762, 4, 4128, 4134))?;
526        check_from_callback(Mode::Public, "credits.aleo", "transfer_public", true, false, count_is!(762, 4, 4128, 4134))?;
527
528        // Dynamic response with records.
529        check_from_callback(Mode::Public, "test.aleo", "foo", true, true, count_is!(3251, 5, 13618, 13633))?;
530        check_from_callback(Mode::Public, "credits.aleo", "transfer_public", true, true, count_is!(3119, 5, 13790, 13805))?;
531
532        Ok(())
533    }
534
535    #[test]
536    #[rustfmt::skip]
537    fn test_from_callback_private() -> Result<()> {
538        // Static response without records.
539        check_from_callback(Mode::Private, "test.aleo", "foo", false, false, count_is!(<=19917, 4, 4108, 4114))?;
540        check_from_callback(Mode::Private, "credits.aleo", "transfer_public", false, false, count_is!(1529, 4, 4108, 4114))?;
541
542        // Static response with records.
543        check_from_callback(Mode::Private, "test.aleo", "foo", false, true, count_is!(<=15809, 5, 13475, 13490))?;
544        check_from_callback(Mode::Private, "credits.aleo", "transfer_public", false, true, count_is!(4046, 5, 13577, 13592))?;
545
546        // Dynamic response without records.
547        check_from_callback(Mode::Private, "test.aleo", "foo", true, false, count_is!(762, 4, 4128, 4134))?;
548        check_from_callback(Mode::Private, "credits.aleo", "transfer_public", true, false, count_is!(762, 4, 4128, 4134))?;
549
550        // Dynamic response with records.
551        check_from_callback(Mode::Private, "test.aleo", "foo", true, true, count_is!(3251, 5, 13618, 13633))?;
552        check_from_callback(Mode::Private, "credits.aleo", "transfer_public", true, true, count_is!(3119, 5, 13790, 13805))?;
553
554        Ok(())
555    }
556}