Skip to main content

snarkvm_synthesizer_program/traits/
stack_and_registers.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 std::{collections::HashSet, sync::Arc};
17
18use crate::{FinalizeGlobalState, FinalizeStoreTrait, Function, Operand, Program};
19use console::{
20    account::Group,
21    network::Network,
22    prelude::{Result, bail},
23    program::{
24        Future,
25        Identifier,
26        Literal,
27        Locator,
28        Plaintext,
29        PlaintextType,
30        ProgramID,
31        Record,
32        Register,
33        RegisterType,
34        Request,
35        Value,
36        ValueType,
37    },
38    types::{Address, Field, U8, U16},
39};
40use rand::{CryptoRng, Rng};
41use snarkvm_synthesizer_snark::{ProvingKey, VerifyingKey};
42
43/// This trait is intended to be implemented only by `snarkvm_synthesizer_process::Stack`.
44///
45/// We make it a trait only to avoid circular dependencies.
46pub trait StackTrait<N: Network> {
47    /// Returns `true` if the proving key for the given name exists.
48    /// The name can be a function name or a record name (for translation keys).
49    fn contains_proving_key(&self, function_or_record_name: &Identifier<N>) -> bool;
50
51    /// Returns the proving key for the given name.
52    /// The name can be a function name or a record name (for translation keys).
53    fn get_proving_key(&self, function_or_record_name: &Identifier<N>) -> Result<ProvingKey<N>>;
54
55    /// Inserts the proving key for the given name.
56    /// The name can be a function name or a record name (for translation keys).
57    fn insert_proving_key(&self, function_or_record_name: &Identifier<N>, proving_key: ProvingKey<N>) -> Result<()>;
58
59    /// Removes the proving key for the given name.
60    /// The name can be a function name or a record name (for translation keys).
61    fn remove_proving_key(&self, function_or_record_name: &Identifier<N>);
62
63    /// Returns `true` if the verifying key for the given name exists.
64    /// The name can be a function name or a record name (for translation keys).
65    fn contains_verifying_key(&self, function_or_record_name: &Identifier<N>) -> bool;
66
67    /// Returns the verifying key for the given name.
68    /// The name can be a function name or a record name (for translation keys).
69    fn get_verifying_key(&self, function_or_record_name: &Identifier<N>) -> Result<VerifyingKey<N>>;
70
71    /// Inserts the verifying key for the given name.
72    /// The name can be a function name or a record name (for translation keys).
73    fn insert_verifying_key(
74        &self,
75        function_or_record_name: &Identifier<N>,
76        verifying_key: VerifyingKey<N>,
77    ) -> Result<()>;
78
79    /// Removes the verifying key for the given name.
80    /// The name can be a function name or a record name (for translation keys).
81    fn remove_verifying_key(&self, function_or_record_name: &Identifier<N>);
82
83    /// Checks that the given value matches the layout of the value type.
84    fn matches_value_type(&self, value: &Value<N>, value_type: &ValueType<N>) -> Result<()>;
85
86    /// Checks that the given stack value matches the layout of the register type.
87    fn matches_register_type(&self, stack_value: &Value<N>, register_type: &RegisterType<N>) -> Result<()>;
88
89    /// Checks that the given record matches the layout of the external record type.
90    fn matches_external_record(&self, record: &Record<N, Plaintext<N>>, locator: &Locator<N>) -> Result<()>;
91
92    /// Checks that the given record matches the layout of the record type.
93    fn matches_record(&self, record: &Record<N, Plaintext<N>>, record_name: &Identifier<N>) -> Result<()>;
94
95    /// Checks that the given plaintext matches the layout of the plaintext type.
96    fn matches_plaintext(&self, plaintext: &Plaintext<N>, plaintext_type: &PlaintextType<N>) -> Result<()>;
97
98    /// Checks that the given future matches the layout of the future type.
99    fn matches_future(&self, future: &Future<N>, locator: &Locator<N>) -> Result<()>;
100
101    /// Returns the program.
102    fn program(&self) -> &Program<N>;
103
104    /// Returns the program ID.
105    fn program_id(&self) -> &ProgramID<N>;
106
107    /// Returns the program address.
108    fn program_address(&self) -> &Address<N>;
109
110    /// Returns the program checksum.
111    fn program_checksum(&self) -> &[U8<N>; 32];
112
113    /// Returns the program checksum as a field element.
114    fn program_checksum_as_field(&self) -> Result<Field<N>>;
115
116    /// Returns the checksum of the program component (function, closure, or view) with the given name.
117    fn component_checksum(&self, name: &Identifier<N>) -> Result<&[U8<N>; 32]>;
118
119    /// Returns the program edition.
120    fn program_edition(&self) -> U16<N>;
121
122    /// Returns the number of amendments for the current program edition.
123    fn program_amendment_count(&self) -> u64;
124
125    /// Sets the number of amendments for the current program edition.
126    fn set_program_amendment_count(&mut self, program_amendment_count: u64);
127
128    /// Returns the program owner.
129    /// The program owner should only be set for programs that are deployed after `ConsensusVersion::V9` is active.
130    fn program_owner(&self) -> &Option<Address<N>>;
131
132    /// Sets the program owner.
133    fn set_program_owner(&mut self, program_owner: Option<Address<N>>);
134
135    /// Returns the external stack for the given program ID.
136    fn get_external_stack(&self, program_id: &ProgramID<N>) -> Result<Arc<Self>>;
137
138    /// Returns the external stack for the given program ID, without checking that:
139    ///
140    /// - The program ID is different from the current program ID.
141    /// - The program ID is imported by the current program.
142    ///
143    /// This function is only to be used for resolution during dynamic dispatch.
144    fn get_stack_global(&self, program_id: &ProgramID<N>) -> Result<Arc<Self>>;
145
146    /// Returns the function with the given function name.
147    fn get_function(&self, function_name: &Identifier<N>) -> Result<Function<N>>;
148
149    /// Returns a reference to the function with the given function name.
150    fn get_function_ref(&self, function_name: &Identifier<N>) -> Result<&Function<N>>;
151
152    /// Returns the minimum number of calls for the given function name.
153    /// Note: In a static call graph (no dynamic dispatch), the minimum is the actual count.
154    fn get_minimum_number_of_calls(&self, function_name: &Identifier<N>) -> Result<usize>;
155
156    /// Returns whether or not a function has a dynamic call in its execution.
157    fn contains_dynamic_call(&self, function_name: &Identifier<N>) -> Result<bool>;
158
159    /// Samples a value for the given value_type.
160    fn sample_value<R: Rng + CryptoRng>(
161        &self,
162        burner_address: &Address<N>,
163        value_type: &RegisterType<N>,
164        rng: &mut R,
165    ) -> Result<Value<N>>;
166
167    /// Returns a record for the given record name, with the given burner address and nonce.
168    fn sample_record<R: Rng + CryptoRng>(
169        &self,
170        burner_address: &Address<N>,
171        record_name: &Identifier<N>,
172        record_nonce: Group<N>,
173        rng: &mut R,
174    ) -> Result<Record<N, Plaintext<N>>>;
175
176    /// Returns a record for the given record name, deriving the nonce from tvk and index.
177    fn sample_record_using_tvk<R: Rng + CryptoRng>(
178        &self,
179        burner_address: &Address<N>,
180        record_name: &Identifier<N>,
181        tvk: Field<N>,
182        index: Field<N>,
183        rng: &mut R,
184    ) -> Result<Record<N, Plaintext<N>>>;
185
186    /// Evaluates a view function on this stack against the given finalize-store state.
187    ///
188    /// The caller (`Call::finalize`) loads operand values from the caller's registers and
189    /// passes them as `inputs`; this method runs the view body and returns its outputs. It
190    /// is the cross-crate hook that lets `Call::finalize` (in `snarkvm-synthesizer-program`)
191    /// dispatch view-call evaluation into `snarkvm-synthesizer-process` without depending on
192    /// concrete `Stack` / `FinalizeRegisters` types.
193    fn evaluate_view(
194        &self,
195        state: FinalizeGlobalState,
196        store: &dyn FinalizeStoreTrait<N>,
197        view_name: &Identifier<N>,
198        inputs: Vec<Value<N>>,
199    ) -> Result<Vec<Value<N>>>;
200}
201
202/// Are the two types either the same, or both structurally equivalent `PlaintextType`s?
203pub fn register_types_equivalent<N: Network>(
204    stack0: &impl StackTrait<N>,
205    type0: &RegisterType<N>,
206    stack1: &impl StackTrait<N>,
207    type1: &RegisterType<N>,
208) -> Result<bool> {
209    use RegisterType::*;
210    if let (Plaintext(plaintext0), Plaintext(plaintext1)) = (type0, type1) {
211        types_equivalent(stack0, plaintext0, stack1, plaintext1)
212    } else {
213        Ok(type0 == type1)
214    }
215}
216
217/// Determines whether two `PlaintextType` values are equivalent.
218///
219/// Equivalence of literals means they're the same type.
220///
221/// Equivalence of structs means they have the same local names (regardless of whether
222/// they're local or external), and their members have the same names and equivalent
223/// types in the same order, recursively.
224///
225/// Equivalence of arrays means they have the same length and their element types are
226/// equivalent.
227///
228/// This definition of equivalence was chosen to balance these concerns:
229///
230/// 1. All programs from before the existence of external structs will continue to work;
231///    thus it's necessary for a struct created from another program to be considered equivalent
232///    to a local one with the same name and structure, as in practice that was the behavior.
233/// 2. We don't want to allow a fork. Thus we do need to check names, not just structural
234///    equivalence - otherwise we could get a program deployable to a node which is using
235///    this check, but not deployable to a node running an earlier SnarkVM.
236///
237/// The stacks are passed because struct types need to access their stack to get their
238/// structure.
239pub fn types_equivalent<N: Network>(
240    stack0: &impl StackTrait<N>,
241    type0: &PlaintextType<N>,
242    stack1: &impl StackTrait<N>,
243    type1: &PlaintextType<N>,
244) -> Result<bool> {
245    // Track the `(program0, program1, struct)` triples that have already been confirmed equivalent, so that a
246    // struct shared by many members is compared at most once. Without this, a program in which every member of
247    // each struct refers to the same earlier struct forms an acyclic graph with exponentially many paths, and the
248    // walk below would make up to `MAX_STRUCT_ENTRIES ^ MAX_STRUCTS` recursive calls.
249    // `in_progress` holds the pairs currently on the recursion path, and re-entering one means the cross-program
250    // struct graph is cyclic.
251    let mut confirmed = HashSet::new();
252    let mut in_progress = HashSet::new();
253    types_equivalent_inner(stack0, type0, stack1, type1, &mut confirmed, &mut in_progress)
254}
255
256/// The memoized inner traversal for [`types_equivalent`].
257fn types_equivalent_inner<N: Network>(
258    stack0: &impl StackTrait<N>,
259    type0: &PlaintextType<N>,
260    stack1: &impl StackTrait<N>,
261    type1: &PlaintextType<N>,
262    confirmed: &mut HashSet<(ProgramID<N>, ProgramID<N>, Identifier<N>)>,
263    in_progress: &mut HashSet<(ProgramID<N>, ProgramID<N>, Identifier<N>)>,
264) -> Result<bool> {
265    use PlaintextType::*;
266
267    // Equivalence requires equal struct names, so each arm below compares one name across the two stacks.
268    match (type0, type1) {
269        (Array(array0), Array(array1)) => Ok(array0.length() == array1.length()
270            && types_equivalent_inner(
271                stack0,
272                array0.next_element_type(),
273                stack1,
274                array1.next_element_type(),
275                confirmed,
276                in_progress,
277            )?),
278        (Literal(lit0), Literal(lit1)) => Ok(lit0 == lit1),
279        (Struct(id0), Struct(id1)) => match id0 == id1 {
280            true => structs_equivalent_inner(stack0, stack1, id0, confirmed, in_progress),
281            false => Ok(false),
282        },
283        (ExternalStruct(loc0), ExternalStruct(loc1)) => match loc0.resource() == loc1.resource() {
284            true => structs_equivalent_inner(
285                &*stack0.get_external_stack(loc0.program_id())?,
286                &*stack1.get_external_stack(loc1.program_id())?,
287                loc0.resource(),
288                confirmed,
289                in_progress,
290            ),
291            false => Ok(false),
292        },
293        (ExternalStruct(loc), Struct(id)) => match loc.resource() == id {
294            true => structs_equivalent_inner(
295                &*stack0.get_external_stack(loc.program_id())?,
296                stack1,
297                id,
298                confirmed,
299                in_progress,
300            ),
301            false => Ok(false),
302        },
303        (Struct(id), ExternalStruct(loc)) => match id == loc.resource() {
304            true => structs_equivalent_inner(
305                stack0,
306                &*stack1.get_external_stack(loc.program_id())?,
307                id,
308                confirmed,
309                in_progress,
310            ),
311            false => Ok(false),
312        },
313        _ => Ok(false),
314    }
315}
316
317/// Compares the struct named `name` in `stack0` against the one of that name in `stack1`, threading the
318/// memoization set from [`types_equivalent_inner`].
319///
320/// `in_progress` holds the pairs currently on the recursion path, and re-entering one means the cross-program
321/// struct graph is cyclic.
322///
323/// The key is `(program0, program1, name)`, which uniquely identifies the pair being compared, since a name
324/// resolves to one struct per program.
325fn structs_equivalent_inner<N: Network>(
326    stack0: &impl StackTrait<N>,
327    stack1: &impl StackTrait<N>,
328    name: &Identifier<N>,
329    confirmed: &mut HashSet<(ProgramID<N>, ProgramID<N>, Identifier<N>)>,
330    in_progress: &mut HashSet<(ProgramID<N>, ProgramID<N>, Identifier<N>)>,
331) -> Result<bool> {
332    // If this pair of structs has already been confirmed equivalent, there is nothing left to compare.
333    let key = (*stack0.program_id(), *stack1.program_id(), *name);
334    if confirmed.contains(&key) {
335        return Ok(true);
336    }
337
338    // Reject cyclic struct references instead of recursing until the process exhausts its stack.
339    if !in_progress.insert(key) {
340        bail!(
341            "Cyclic struct reference detected while comparing '{name}' in '{}' and '{}'",
342            stack0.program_id(),
343            stack1.program_id()
344        )
345    }
346
347    let result: Result<bool> = (|| {
348        let st0 = stack0.program().get_struct(name)?;
349        let st1 = stack1.program().get_struct(name)?;
350
351        if st0.members().len() != st1.members().len() {
352            return Ok(false);
353        }
354
355        for ((name0, type0), (name1, type1)) in st0.members().iter().zip(st1.members()) {
356            if name0 != name1 || !types_equivalent_inner(stack0, type0, stack1, type1, confirmed, in_progress)? {
357                return Ok(false);
358            }
359        }
360
361        Ok(true)
362    })();
363
364    in_progress.remove(&key);
365
366    if !result? {
367        return Ok(false);
368    }
369
370    // Record only confirmed equivalences: a mismatch short-circuits the whole comparison to `false`, so a hit
371    // above always denotes a genuine equivalence, and the memoization changes only the running time.
372    confirmed.insert(key);
373    Ok(true)
374}
375
376pub trait FinalizeRegistersState<N: Network>: RegistersTrait<N> {
377    /// Returns the global state for the finalize scope.
378    fn state(&self) -> &FinalizeGlobalState;
379
380    /// Returns the transition ID for the finalize scope, if one is associated with this scope.
381    /// View functions are externally-callable and have no associated transition, so this is
382    /// `None` on the view path; finalize and constructor scopes always have `Some(...)`.
383    fn transition_id(&self) -> Option<&N::TransitionID>;
384
385    /// Returns the function name for the finalize scope.
386    fn function_name(&self) -> &Identifier<N>;
387
388    /// Returns the nonce for the finalize registers, if one is associated with this scope.
389    /// `None` on the view path (no transition → no nonce); always `Some(...)` on finalize.
390    fn nonce(&self) -> Option<u64>;
391}
392
393pub trait RegistersSigner<N: Network>: RegistersTrait<N> {
394    /// Returns the transition signer.
395    fn signer(&self) -> Result<Address<N>>;
396
397    /// Sets the transition signer.
398    fn set_signer(&mut self, signer: Address<N>);
399
400    /// Returns the root transition view key.
401    fn root_tvk(&self) -> Result<Field<N>>;
402
403    /// Sets the root transition view key.
404    fn set_root_tvk(&mut self, root_tvk: Field<N>);
405
406    /// Returns the transition caller.
407    fn caller(&self) -> Result<Address<N>>;
408
409    /// Sets the transition caller.
410    fn set_caller(&mut self, caller: Address<N>);
411
412    /// Returns the transition view key.
413    fn tvk(&self) -> Result<Field<N>>;
414
415    /// Sets the transition view key.
416    fn set_tvk(&mut self, tvk: Field<N>);
417
418    /// Returns the request.
419    fn request(&self) -> Result<&Request<N>>;
420
421    /// Sets the request.
422    fn set_request(&mut self, request: Request<N>);
423}
424
425pub trait RegistersTrait<N: Network> {
426    /// Loads the value of a given operand.
427    ///
428    /// # Errors
429    /// This method should halt if the register locator is not found.
430    /// In the case of register members, this method should halt if the member is not found.
431    fn load(&self, stack: &impl StackTrait<N>, operand: &Operand<N>) -> Result<Value<N>>;
432
433    /// Loads the literal of a given operand.
434    ///
435    /// # Errors
436    /// This method should halt if the given operand is not a literal.
437    /// This method should halt if the register locator is not found.
438    /// In the case of register members, this method should halt if the member is not found.
439    fn load_literal(&self, stack: &impl StackTrait<N>, operand: &Operand<N>) -> Result<Literal<N>> {
440        match self.load(stack, operand)? {
441            Value::Plaintext(Plaintext::Literal(literal, ..)) => Ok(literal),
442            Value::Plaintext(Plaintext::Struct(..))
443            | Value::Plaintext(Plaintext::Array(..))
444            | Value::Record(..)
445            | Value::Future(..)
446            | Value::DynamicRecord(..)
447            | Value::DynamicFuture(..) => {
448                bail!("Operand must be a literal")
449            }
450        }
451    }
452
453    /// Loads the plaintext of a given operand.
454    ///
455    /// # Errors
456    /// This method should halt if the given operand is not a plaintext.
457    /// This method should halt if the register locator is not found.
458    /// In the case of register members, this method should halt if the member is not found.
459    fn load_plaintext(&self, stack: &impl StackTrait<N>, operand: &Operand<N>) -> Result<Plaintext<N>> {
460        match self.load(stack, operand)? {
461            Value::Plaintext(plaintext) => Ok(plaintext),
462            Value::Record(..) | Value::Future(..) | Value::DynamicRecord(..) | Value::DynamicFuture(..) => {
463                bail!("Operand must be a plaintext")
464            }
465        }
466    }
467
468    /// Assigns the given value to the given register, assuming the register is not already assigned.
469    ///
470    /// # Errors
471    /// This method should halt if the given register is a register member.
472    /// This method should halt if the given register is an input register.
473    /// This method should halt if the register is already used.
474    fn store(&mut self, stack: &impl StackTrait<N>, register: &Register<N>, stack_value: Value<N>) -> Result<()>;
475
476    /// Assigns the given literal to the given register, assuming the register is not already assigned.
477    ///
478    /// # Errors
479    /// This method should halt if the given register is a register member.
480    /// This method should halt if the given register is an input register.
481    /// This method should halt if the register is already used.
482    fn store_literal(&mut self, stack: &impl StackTrait<N>, register: &Register<N>, literal: Literal<N>) -> Result<()> {
483        self.store(stack, register, Value::Plaintext(Plaintext::from(literal)))
484    }
485}
486
487/// This trait is intended to be implemented only by `snarkvm_synthesizer_process::Registers`.
488///
489/// We make it a trait only to avoid circular dependencies.
490pub trait RegistersCircuit<N: Network, A: circuit::Aleo<Network = N>> {
491    /// Returns the transition signer, as a circuit.
492    fn signer_circuit(&self) -> Result<circuit::Address<A>>;
493
494    /// Sets the transition signer, as a circuit.
495    fn set_signer_circuit(&mut self, signer_circuit: circuit::Address<A>);
496
497    /// Returns the root transition view key, as a circuit.
498    fn root_tvk_circuit(&self) -> Result<circuit::Field<A>>;
499
500    /// Sets the root transition view key, as a circuit.
501    fn set_root_tvk_circuit(&mut self, root_tvk_circuit: circuit::Field<A>);
502
503    /// Returns the transition caller, as a circuit.
504    fn caller_circuit(&self) -> Result<circuit::Address<A>>;
505
506    /// Sets the transition caller, as a circuit.
507    fn set_caller_circuit(&mut self, caller_circuit: circuit::Address<A>);
508
509    /// Returns the transition view key, as a circuit.
510    fn tvk_circuit(&self) -> Result<circuit::Field<A>>;
511
512    /// Sets the transition view key, as a circuit.
513    fn set_tvk_circuit(&mut self, tvk_circuit: circuit::Field<A>);
514
515    /// Loads the value of a given operand.
516    ///
517    /// # Errors
518    /// This method should halt if the register locator is not found.
519    /// In the case of register members, this method should halt if the member is not found.
520    fn load_circuit(&self, stack: &impl StackTrait<N>, operand: &Operand<N>) -> Result<circuit::Value<A>>;
521
522    /// Loads the literal of a given operand.
523    ///
524    /// # Errors
525    /// This method should halt if the given operand is not a literal.
526    /// This method should halt if the register locator is not found.
527    /// In the case of register members, this method should halt if the member is not found.
528    fn load_literal_circuit(&self, stack: &impl StackTrait<N>, operand: &Operand<N>) -> Result<circuit::Literal<A>> {
529        match self.load_circuit(stack, operand)? {
530            circuit::Value::Plaintext(circuit::Plaintext::Literal(literal, ..)) => Ok(literal),
531            circuit::Value::Plaintext(circuit::Plaintext::Struct(..))
532            | circuit::Value::Plaintext(circuit::Plaintext::Array(..))
533            | circuit::Value::Record(..)
534            | circuit::Value::Future(..)
535            | circuit::Value::DynamicRecord(..)
536            | circuit::Value::DynamicFuture(..) => bail!("Operand must be a literal"),
537        }
538    }
539
540    /// Loads the plaintext of a given operand.
541    ///
542    /// # Errors
543    /// This method should halt if the given operand is not a plaintext.
544    /// This method should halt if the register locator is not found.
545    /// In the case of register members, this method should halt if the member is not found.
546    fn load_plaintext_circuit(
547        &self,
548        stack: &impl StackTrait<N>,
549        operand: &Operand<N>,
550    ) -> Result<circuit::Plaintext<A>> {
551        match self.load_circuit(stack, operand)? {
552            circuit::Value::Plaintext(plaintext) => Ok(plaintext),
553            circuit::Value::Record(..)
554            | circuit::Value::Future(..)
555            | circuit::Value::DynamicRecord(..)
556            | circuit::Value::DynamicFuture(..) => bail!("Operand must be a plaintext"),
557        }
558    }
559
560    /// Assigns the given value to the given register, assuming the register is not already assigned.
561    ///
562    /// # Errors
563    /// This method should halt if the given register is a register member.
564    /// This method should halt if the given register is an input register.
565    /// This method should halt if the register is already used.
566    fn store_circuit(
567        &mut self,
568        stack: &impl StackTrait<N>,
569        register: &Register<N>,
570        stack_value: circuit::Value<A>,
571    ) -> Result<()>;
572
573    /// Assigns the given literal to the given register, assuming the register is not already assigned.
574    ///
575    /// # Errors
576    /// This method should halt if the given register is a register member.
577    /// This method should halt if the given register is an input register.
578    /// This method should halt if the register is already used.
579    fn store_literal_circuit(
580        &mut self,
581        stack: &impl StackTrait<N>,
582        register: &Register<N>,
583        literal: circuit::Literal<A>,
584    ) -> Result<()> {
585        self.store_circuit(stack, register, circuit::Value::Plaintext(circuit::Plaintext::from(literal)))
586    }
587
588    /// Checks that the given circuit value matches the layout of the register type. This is a circuit analogue of [`StackTrait::matches_register_type`].
589    fn circuit_matches_register_type(
590        stack: &impl StackTrait<N>,
591        circuit_value: &circuit::Value<A>,
592        register_type: &RegisterType<N>,
593    ) -> Result<()>;
594}