diff --git a/circuit/program/src/data/value/mod.rs b/circuit/program/src/data/value/mod.rs index 0187140128..e3ac4bee0b 100644 --- a/circuit/program/src/data/value/mod.rs +++ b/circuit/program/src/data/value/mod.rs @@ -68,3 +68,24 @@ impl Eject for Value { } } } + +impl From> for Value { + /// Initializes the value from a plaintext. + fn from(plaintext: Plaintext) -> Self { + Self::Plaintext(plaintext) + } +} + +impl From>> for Value { + /// Initializes the value from a record. + fn from(record: Record>) -> Self { + Self::Record(record) + } +} + +impl From> for Value { + /// Initializes the value from a future. + fn from(future: Future) -> Self { + Self::Future(future) + } +} diff --git a/console/program/src/data_types/array_type/bytes.rs b/console/program/src/data_types/array_type/bytes.rs index 60108d8264..001ad36290 100644 --- a/console/program/src/data_types/array_type/bytes.rs +++ b/console/program/src/data_types/array_type/bytes.rs @@ -14,7 +14,7 @@ // limitations under the License. use super::*; -use crate::{Identifier, LiteralType}; +use crate::{Identifier, LiteralType, Locator}; impl FromBytes for ArrayType { fn read_le(mut reader: R) -> IoResult { @@ -23,7 +23,8 @@ impl FromBytes for ArrayType { let element_type = match variant { 0 => PlaintextType::Literal(LiteralType::read_le(&mut reader)?), 1 => PlaintextType::Struct(Identifier::read_le(&mut reader)?), - 2.. => return Err(error(format!("Failed to deserialize element type {variant}"))), + 2 => PlaintextType::ExternalStruct(Locator::read_le(&mut reader)?), + _ => return Err(error(format!("Failed to deserialize element type {variant}"))), }; // Read the number of dimensions of the array. @@ -58,7 +59,7 @@ impl ToBytes for ArrayType { // Note that the lengths are in the order of the outermost dimension to the innermost dimension. for _ in 1..N::MAX_DATA_DEPTH { element_type = match element_type { - PlaintextType::Literal(_) | PlaintextType::Struct(_) => break, + PlaintextType::Literal(_) | PlaintextType::Struct(_) | PlaintextType::ExternalStruct(_) => break, PlaintextType::Array(array_type) => { lengths.push(*array_type.length()); array_type.next_element_type().clone() @@ -81,6 +82,10 @@ impl ToBytes for ArrayType { 1u8.write_le(&mut writer)?; identifier.write_le(&mut writer)?; } + PlaintextType::ExternalStruct(locator) => { + 2u8.write_le(&mut writer)?; + locator.write_le(&mut writer)?; + } PlaintextType::Array(_) => { // This is technically unreachable by definition, however we return an error // out of an abundance of caution. diff --git a/console/program/src/data_types/array_type/parse.rs b/console/program/src/data_types/array_type/parse.rs index 1c0de98112..ae58b5b5fe 100644 --- a/console/program/src/data_types/array_type/parse.rs +++ b/console/program/src/data_types/array_type/parse.rs @@ -14,7 +14,7 @@ // limitations under the License. use super::*; -use crate::{Identifier, LiteralType}; +use crate::{Identifier, LiteralType, Locator}; impl Parser for ArrayType { /// Parses a string into a literal type. @@ -22,7 +22,12 @@ impl Parser for ArrayType { fn parse(string: &str) -> ParserResult { // A helper function to parse the innermost element type. fn parse_inner_element_type(string: &str) -> ParserResult> { - alt((map(LiteralType::parse, PlaintextType::from), map(Identifier::parse, PlaintextType::from)))(string) + // Order matters - we shouldn't try to parse Identifier before Locator. + alt(( + map(Locator::parse, PlaintextType::from), + map(LiteralType::parse, PlaintextType::from), + map(Identifier::parse, PlaintextType::from), + ))(string) } // A helper function to parse the length of each dimension. diff --git a/console/program/src/data_types/finalize_type/size_in_bits.rs b/console/program/src/data_types/finalize_type/size_in_bits.rs index c89c6bb26b..11049c2c91 100644 --- a/console/program/src/data_types/finalize_type/size_in_bits.rs +++ b/console/program/src/data_types/finalize_type/size_in_bits.rs @@ -20,26 +20,41 @@ use super::*; impl FinalizeType { /// Returns the number of bits of a finalize type. /// Note. The plaintext variant is assumed to be an argument of a `Future` and this does not have a "raw" serialization. - pub fn future_size_in_bits(locator: &Locator, get_struct: &F0, get_future: &F1) -> Result + pub fn future_size_in_bits( + locator: &Locator, + get_struct: &F0, + get_external_struct: &F1, + get_future: &F2, + ) -> Result where F0: Fn(&Identifier) -> Result>, - F1: Fn(&Locator) -> Result>>, + F1: Fn(&Locator) -> Result>, + F2: Fn(&Locator) -> Result>>, { - FinalizeType::Future(*locator).size_in_bits_internal(get_struct, get_future, 0) + FinalizeType::Future(*locator).size_in_bits_internal(get_struct, get_external_struct, get_future, 0) } /// A helper function to determine the number of bits of a plaintext type, while tracking the depth of the data. /// Note. The plaintext variant is assumed to be an argument of a `Future` and thus does not have a "raw" serialization. - fn size_in_bits_internal(&self, get_struct: &F0, get_future: &F1, depth: usize) -> Result + fn size_in_bits_internal( + &self, + get_struct: &F0, + get_external_struct: &F1, + get_future: &F2, + depth: usize, + ) -> Result where F0: Fn(&Identifier) -> Result>, - F1: Fn(&Locator) -> Result>>, + F1: Fn(&Locator) -> Result>, + F2: Fn(&Locator) -> Result>>, { // Ensure that the depth is within the maximum limit. ensure!(depth <= N::MAX_DATA_DEPTH, "Finalize type depth exceeds maximum limit: {}", N::MAX_DATA_DEPTH); match self { - Self::Plaintext(plaintext_type) => plaintext_type.size_in_bits_internal(get_struct, depth), + Self::Plaintext(plaintext_type) => { + plaintext_type.size_in_bits_internal(get_struct, get_external_struct, depth) + } Self::Future(locator) => { // Initialize the size in bits. let mut size = 0usize; @@ -79,7 +94,12 @@ impl FinalizeType { // Account for the argument bits. size = size - .checked_add(argument.size_in_bits_internal(get_struct, get_future, depth + 1)?) + .checked_add(argument.size_in_bits_internal( + get_struct, + get_external_struct, + get_future, + depth + 1, + )?) .ok_or(anyhow!("`size_in_bits` overflowed"))?; } @@ -89,11 +109,17 @@ impl FinalizeType { } /// Returns the number of raw bits of a finlaize type. - pub fn future_size_in_bits_raw(locator: &Locator, get_struct: &F0, get_future: &F1) -> Result + pub fn future_size_in_bits_raw( + locator: &Locator, + get_struct: &F0, + get_external_struct: &F1, + get_future: &F2, + ) -> Result where F0: Fn(&Identifier) -> Result>, - F1: Fn(&Locator) -> Result>>, + F1: Fn(&Locator) -> Result>, + F2: Fn(&Locator) -> Result>>, { - Self::future_size_in_bits(locator, get_struct, get_future) + Self::future_size_in_bits(locator, get_struct, get_external_struct, get_future) } } diff --git a/console/program/src/data_types/plaintext_type/bytes.rs b/console/program/src/data_types/plaintext_type/bytes.rs index 5251f3ef02..db493b676e 100644 --- a/console/program/src/data_types/plaintext_type/bytes.rs +++ b/console/program/src/data_types/plaintext_type/bytes.rs @@ -23,7 +23,8 @@ impl FromBytes for PlaintextType { 0 => Ok(Self::Literal(LiteralType::read_le(&mut reader)?)), 1 => Ok(Self::Struct(Identifier::read_le(&mut reader)?)), 2 => Ok(Self::Array(ArrayType::read_le(&mut reader)?)), - 3.. => Err(error(format!("Failed to deserialize annotation variant {variant}"))), + 3 => Ok(Self::ExternalStruct(Locator::read_le(&mut reader)?)), + 4.. => Err(error(format!("Failed to deserialize annotation variant {variant}"))), } } } @@ -44,6 +45,10 @@ impl ToBytes for PlaintextType { 2u8.write_le(&mut writer)?; array_type.write_le(&mut writer) } + Self::ExternalStruct(locator) => { + 3u8.write_le(&mut writer)?; + locator.write_le(&mut writer) + } } } } diff --git a/console/program/src/data_types/plaintext_type/mod.rs b/console/program/src/data_types/plaintext_type/mod.rs index 6fd409f575..8fa3eaac18 100644 --- a/console/program/src/data_types/plaintext_type/mod.rs +++ b/console/program/src/data_types/plaintext_type/mod.rs @@ -18,7 +18,7 @@ mod parse; mod serialize; mod size_in_bits; -use crate::{ArrayType, Identifier, LiteralType, StructType}; +use crate::{ArrayType, Identifier, LiteralType, Locator, ProgramID, StructType}; use snarkvm_console_network::prelude::*; /// A `PlaintextType` defines the type parameter for a literal, struct, or array. @@ -27,12 +27,40 @@ pub enum PlaintextType { /// A literal type contains its type name. /// The format of the type is ``. Literal(LiteralType), - /// An struct type contains its identifier. + /// A struct type contains its identifier. /// The format of the type is ``. Struct(Identifier), /// An array type contains its element type and length. /// The format of the type is `[; ]`. Array(ArrayType), + /// An external struct type contains its locator. + /// The format of the type is `/`. + ExternalStruct(Locator), +} + +impl PlaintextType { + /// Returns whether this type refers to an external struct. + pub fn contains_external_struct(&self) -> bool { + use PlaintextType::*; + + match self { + Literal(..) | Struct(..) => false, + ExternalStruct(..) => true, + Array(array_type) => array_type.base_element_type().contains_external_struct(), + } + } + + // Make unqualified structs into external ones with the given `id`. + pub fn qualify(self, id: ProgramID) -> Self { + match self { + PlaintextType::ExternalStruct(..) | PlaintextType::Literal(..) => self, + PlaintextType::Struct(name) => PlaintextType::ExternalStruct(Locator::new(id, name)), + PlaintextType::Array(array_type) => { + let element_type = array_type.next_element_type().clone().qualify(id); + PlaintextType::Array(ArrayType::new(element_type, vec![*array_type.length()]).unwrap()) + } + } + } } impl From for PlaintextType { @@ -49,6 +77,13 @@ impl From> for PlaintextType { } } +impl From> for PlaintextType { + /// Initializes a plaintext type from an external struct type. + fn from(locator: Locator) -> Self { + PlaintextType::ExternalStruct(locator) + } +} + impl From> for PlaintextType { /// Initializes a plaintext type from an array type. fn from(array: ArrayType) -> Self { @@ -76,7 +111,7 @@ impl PlaintextType { /// Returns `true` if the `PlaintextType` is an array and the size exceeds the given maximum. pub fn exceeds_max_array_size(&self, max_array_size: u32) -> bool { match self { - Self::Literal(_) | Self::Struct(_) => false, + Self::Literal(_) | Self::Struct(_) | Self::ExternalStruct(_) => false, Self::Array(array_type) => array_type.exceeds_max_array_size(max_array_size), } } diff --git a/console/program/src/data_types/plaintext_type/parse.rs b/console/program/src/data_types/plaintext_type/parse.rs index 74a9ce1a38..6de10b1ed2 100644 --- a/console/program/src/data_types/plaintext_type/parse.rs +++ b/console/program/src/data_types/plaintext_type/parse.rs @@ -21,7 +21,9 @@ impl Parser for PlaintextType { fn parse(string: &str) -> ParserResult { // Parse to determine the plaintext type (order matters). alt(( + // Order matters - we shouldn't try to parse Identifier before Locator. map(ArrayType::parse, |type_| Self::Array(type_)), + map(Locator::parse, |locator| Self::ExternalStruct(locator)), map(Identifier::parse, |identifier| Self::Struct(identifier)), map(LiteralType::parse, |type_| Self::Literal(type_)), ))(string) @@ -60,6 +62,8 @@ impl Display for PlaintextType { Self::Literal(literal) => Display::fmt(literal, f), // Prints the struct, i.e. signature Self::Struct(struct_) => Display::fmt(struct_, f), + // Prints the external struct, i.e. foo.aleo/bar + Self::ExternalStruct(locator) => Display::fmt(locator, f), // Prints the array type, i.e. [field; 2u32] Self::Array(array) => Display::fmt(array, f), } diff --git a/console/program/src/data_types/plaintext_type/size_in_bits.rs b/console/program/src/data_types/plaintext_type/size_in_bits.rs index 2dd39b5c71..843c3efaf3 100644 --- a/console/program/src/data_types/plaintext_type/size_in_bits.rs +++ b/console/program/src/data_types/plaintext_type/size_in_bits.rs @@ -17,17 +17,24 @@ use super::*; impl PlaintextType { /// Returns the number of bits of a plaintext type. - pub fn size_in_bits(&self, get_struct: &F) -> Result + pub fn size_in_bits(&self, get_struct: &F0, get_external_struct: &F1) -> Result where - F: Fn(&Identifier) -> Result>, + F0: Fn(&Identifier) -> Result>, + F1: Fn(&Locator) -> Result>, { - self.size_in_bits_internal(get_struct, 0) + self.size_in_bits_internal(get_struct, get_external_struct, 0) } /// A helper function to determine the number of bits of a plaintext type, while tracking the depth of the data. - pub(crate) fn size_in_bits_internal(&self, get_struct: &F, depth: usize) -> Result + pub(crate) fn size_in_bits_internal( + &self, + get_struct: &F0, + get_external_struct: &F1, + depth: usize, + ) -> Result where - F: Fn(&Identifier) -> Result>, + F0: Fn(&Identifier) -> Result>, + F1: Fn(&Locator) -> Result>, { // Ensure that the depth is within the maximum limit. ensure!(depth <= N::MAX_DATA_DEPTH, "Plaintext depth exceeds maximum limit: {}", N::MAX_DATA_DEPTH); @@ -67,7 +74,32 @@ impl PlaintextType { // Account for the size of the member total = total.checked_add(16).ok_or(anyhow!("`size_in_bits` overflowed"))?; // Account for the member itself. - let member_size = member_type.size_in_bits_internal(get_struct, depth + 1)?; + let member_size = member_type.size_in_bits_internal(get_struct, get_external_struct, depth + 1)?; + total = total.checked_add(member_size).ok_or(anyhow!("`size_in_bits` overflowed"))?; + } + + Ok(total) + } + PlaintextType::ExternalStruct(locator) => { + // Look up the struct + let struct_ = get_external_struct(locator)?; + + // Account for the plaintext variant bits. + let mut total = PlaintextType::::STRUCT_PREFIX_BITS.len(); + // Account for the number of members in the struct. + total = total.checked_add(8).ok_or(anyhow!("`size_in_bits` overflowed"))?; + // Add up the sizes of each member. + for (identifier, member_type) in struct_.members() { + // Account for the size of the identifier. + total = total.checked_add(8).ok_or(anyhow!("`size_in_bits` overflowed"))?; + // Account for the identifier. + total = total + .checked_add(identifier.size_in_bits() as usize) + .ok_or(anyhow!("`size_in_bits` overflowed"))?; + // Account for the size of the member + total = total.checked_add(16).ok_or(anyhow!("`size_in_bits` overflowed"))?; + // Account for the member itself. + let member_size = member_type.size_in_bits_internal(get_struct, get_external_struct, depth + 1)?; total = total.checked_add(member_size).ok_or(anyhow!("`size_in_bits` overflowed"))?; } @@ -79,7 +111,8 @@ impl PlaintextType { // Account for the size of the array length. total = total.checked_add(32).ok_or(anyhow!("`size_in_bits` overflowed"))?; // Get the size of the element type. - let element_size = array_type.next_element_type().size_in_bits_internal(get_struct, depth + 1)?; + let element_size = + array_type.next_element_type().size_in_bits_internal(get_struct, get_external_struct, depth + 1)?; // Get the total size of an element. let element_total = 16usize.checked_add(element_size).ok_or(anyhow!("`size_in_bits` overflowed"))?; // Multiply by the length of the array, ensuring no overflow occurs. @@ -97,17 +130,24 @@ impl PlaintextType { } /// Returns the number of raw bits of a plaintext type. - pub fn size_in_bits_raw(&self, get_struct: &F) -> Result + pub fn size_in_bits_raw(&self, get_struct: &F0, get_external_struct: &F1) -> Result where - F: Fn(&Identifier) -> Result>, + F0: Fn(&Identifier) -> Result>, + F1: Fn(&Locator) -> Result>, { - self.size_in_bits_raw_internal(get_struct, 0) + self.size_in_bits_raw_internal(get_struct, get_external_struct, 0) } // A helper function to determine the number of raw bits of a plaintext type, while tracking the depth of the data. - fn size_in_bits_raw_internal(&self, get_struct: &F, depth: usize) -> Result + fn size_in_bits_raw_internal( + &self, + get_struct: &F0, + get_external_struct: &F1, + depth: usize, + ) -> Result where - F: Fn(&Identifier) -> Result>, + F0: Fn(&Identifier) -> Result>, + F1: Fn(&Locator) -> Result>, { // Ensure that the depth is within the maximum limit. ensure!(depth <= N::MAX_DATA_DEPTH, "Plaintext depth exceeds maximum limit: {}", N::MAX_DATA_DEPTH); @@ -121,7 +161,22 @@ impl PlaintextType { let mut total = 0usize; for member_type in struct_.members().values() { // Get the size of the member. - let member_size = member_type.size_in_bits_raw_internal(get_struct, depth + 1)?; + let member_size = + member_type.size_in_bits_raw_internal(get_struct, get_external_struct, depth + 1)?; + // Add to the total size, ensuring no overflow occurs. + total = total.checked_add(member_size).ok_or(anyhow!("`size_in_bits_raw` overflowed"))?; + } + Ok(total) + } + PlaintextType::ExternalStruct(locator) => { + // Look up the struct. + let struct_ = get_external_struct(locator)?; + // Add up the sizes of each member. + let mut total = 0usize; + for member_type in struct_.members().values() { + // Get the size of the member. + let member_size = + member_type.size_in_bits_raw_internal(get_struct, get_external_struct, depth + 1)?; // Add to the total size, ensuring no overflow occurs. total = total.checked_add(member_size).ok_or(anyhow!("`size_in_bits_raw` overflowed"))?; } @@ -129,7 +184,11 @@ impl PlaintextType { } PlaintextType::Array(array_type) => { // Get the size of the element type. - let element_size = array_type.next_element_type().size_in_bits_raw_internal(get_struct, depth + 1)?; + let element_size = array_type.next_element_type().size_in_bits_raw_internal( + get_struct, + get_external_struct, + depth + 1, + )?; // Multiply by the length of the array, ensuring no overflow occurs. let total = element_size .checked_mul(**array_type.length() as usize) diff --git a/console/program/src/data_types/record_type/size_in_bits.rs b/console/program/src/data_types/record_type/size_in_bits.rs index 130dbf2064..3fe1996d06 100644 --- a/console/program/src/data_types/record_type/size_in_bits.rs +++ b/console/program/src/data_types/record_type/size_in_bits.rs @@ -15,15 +15,16 @@ use snarkvm_console_types::{Address, Group, U8}; -use crate::StructType; +use crate::{Locator, StructType}; use super::*; impl RecordType { /// Returns the number of bits of a record type. - pub fn size_in_bits(&self, get_struct: &F) -> Result + pub fn size_in_bits(&self, get_struct: &F0, get_external_struct: &F1) -> Result where - F: Fn(&Identifier) -> Result>, + F0: Fn(&Identifier) -> Result>, + F1: Fn(&Locator) -> Result>, { // Initialize the counter. let mut size = 0usize; @@ -45,7 +46,7 @@ impl RecordType { data_size = data_size.checked_add(2).ok_or(anyhow!("`size_in_bits` overflowed"))?; // Account for the entry data bits. data_size = data_size - .checked_add(entry_type.plaintext_type().size_in_bits(get_struct)?) + .checked_add(entry_type.plaintext_type().size_in_bits(get_struct, get_external_struct)?) .ok_or(anyhow!("`size_in_bits` overflowed"))?; } @@ -73,10 +74,11 @@ impl RecordType { } /// Returns the number of raw bits of a record type. - pub fn size_in_bits_raw(&self, get_struct: &F) -> Result + pub fn size_in_bits_raw(&self, get_struct: &F0, get_external_struct: &F1) -> Result where - F: Fn(&Identifier) -> Result>, + F0: Fn(&Identifier) -> Result>, + F1: Fn(&Locator) -> Result>, { - self.size_in_bits(get_struct) + self.size_in_bits(get_struct, get_external_struct) } } diff --git a/console/program/src/data_types/register_type/mod.rs b/console/program/src/data_types/register_type/mod.rs index 4f40162a4c..0a51ad9c6e 100644 --- a/console/program/src/data_types/register_type/mod.rs +++ b/console/program/src/data_types/register_type/mod.rs @@ -18,7 +18,7 @@ mod parse; mod serialize; mod size_in_bits; -use crate::{FinalizeType, Identifier, Locator, PlaintextType, ValueType}; +use crate::{FinalizeType, Identifier, Locator, PlaintextType, ProgramID, ValueType}; use snarkvm_console_network::prelude::*; use enum_index::EnumIndex; @@ -35,6 +35,22 @@ pub enum RegisterType { Future(Locator), } +impl RegisterType { + // Make unqualified structs or records into external ones with the given `id`. + pub fn qualify(self, id: ProgramID) -> Self { + match self { + RegisterType::Plaintext(plaintext_type) => RegisterType::Plaintext(plaintext_type.qualify(id)), + RegisterType::Record(name) => RegisterType::ExternalRecord(Locator::new(id, name)), + RegisterType::ExternalRecord(..) | RegisterType::Future(..) => self, + } + } + + /// Returns whether this type refers to an external struct. + pub fn contains_external_struct(&self) -> bool { + matches!(self, RegisterType::Plaintext(t) if t.contains_external_struct()) + } +} + impl From> for RegisterType { /// Converts a value type to a register type. fn from(value: ValueType) -> Self { diff --git a/console/program/src/data_types/register_type/size_in_bits.rs b/console/program/src/data_types/register_type/size_in_bits.rs index 91742b781b..97e195347d 100644 --- a/console/program/src/data_types/register_type/size_in_bits.rs +++ b/console/program/src/data_types/register_type/size_in_bits.rs @@ -19,46 +19,60 @@ use super::*; impl RegisterType { /// Returns the number of bits of a register type. - pub fn size_in_bits( + pub fn size_in_bits( &self, get_struct: &F0, - get_record: &F1, - get_external_record: &F2, - get_future: &F3, + get_external_struct: &F1, + get_record: &F2, + get_external_record: &F3, + get_future: &F4, ) -> Result where F0: Fn(&Identifier) -> Result>, - F1: Fn(&Identifier) -> Result>, - F2: Fn(&Locator) -> Result>, - F3: Fn(&Locator) -> Result>>, + F1: Fn(&Locator) -> Result>, + F2: Fn(&Identifier) -> Result>, + F3: Fn(&Locator) -> Result>, + F4: Fn(&Locator) -> Result>>, { match self { - RegisterType::Plaintext(plaintext_type) => plaintext_type.size_in_bits(get_struct), - RegisterType::Record(identifier) => get_record(identifier)?.size_in_bits(get_struct), - RegisterType::ExternalRecord(locator) => get_external_record(locator)?.size_in_bits(get_struct), - RegisterType::Future(locator) => FinalizeType::future_size_in_bits(locator, get_struct, get_future), + RegisterType::Plaintext(plaintext_type) => plaintext_type.size_in_bits(get_struct, get_external_struct), + RegisterType::Record(identifier) => get_record(identifier)?.size_in_bits(get_struct, get_external_struct), + RegisterType::ExternalRecord(locator) => { + get_external_record(locator)?.size_in_bits(get_struct, get_external_struct) + } + RegisterType::Future(locator) => { + FinalizeType::future_size_in_bits(locator, get_struct, get_external_struct, get_future) + } } } /// Returns the number of raw bits of a register type. - pub fn size_in_bits_raw( + pub fn size_in_bits_raw( &self, get_struct: &F0, - get_record: &F1, - get_external_record: &F2, - get_future: &F3, + get_external_struct: &F1, + get_record: &F2, + get_external_record: &F3, + get_future: &F4, ) -> Result where F0: Fn(&Identifier) -> Result>, - F1: Fn(&Identifier) -> Result>, - F2: Fn(&Locator) -> Result>, - F3: Fn(&Locator) -> Result>>, + F1: Fn(&Locator) -> Result>, + F2: Fn(&Identifier) -> Result>, + F3: Fn(&Locator) -> Result>, + F4: Fn(&Locator) -> Result>>, { match self { - RegisterType::Plaintext(plaintext_type) => plaintext_type.size_in_bits_raw(get_struct), - RegisterType::Record(identifier) => get_record(identifier)?.size_in_bits_raw(get_struct), - RegisterType::ExternalRecord(locator) => get_external_record(locator)?.size_in_bits_raw(get_struct), - RegisterType::Future(locator) => FinalizeType::future_size_in_bits_raw(locator, get_struct, get_future), + RegisterType::Plaintext(plaintext_type) => plaintext_type.size_in_bits_raw(get_struct, get_external_struct), + RegisterType::Record(identifier) => { + get_record(identifier)?.size_in_bits_raw(get_struct, get_external_struct) + } + RegisterType::ExternalRecord(locator) => { + get_external_record(locator)?.size_in_bits_raw(get_struct, get_external_struct) + } + RegisterType::Future(locator) => { + FinalizeType::future_size_in_bits_raw(locator, get_struct, get_external_struct, get_future) + } } } } diff --git a/console/program/src/data_types/value_type/mod.rs b/console/program/src/data_types/value_type/mod.rs index 3f2928c0ff..e9ca1e5bcd 100644 --- a/console/program/src/data_types/value_type/mod.rs +++ b/console/program/src/data_types/value_type/mod.rs @@ -52,6 +52,15 @@ impl ValueType { ValueType::Future(..) => 5, } } + + /// Returns whether this type references an external struct. + pub fn contains_external_struct(&self) -> bool { + use ValueType::*; + matches!( + self, + Constant(plaintext) | Public(plaintext) | Private(plaintext) if plaintext.contains_external_struct() + ) + } } impl From> for ValueType { diff --git a/synthesizer/process/src/cost.rs b/synthesizer/process/src/cost.rs index 50aa4e8ad5..c1f9c96de2 100644 --- a/synthesizer/process/src/cost.rs +++ b/synthesizer/process/src/cost.rs @@ -432,6 +432,10 @@ fn plaintext_size_in_bytes(stack: &Stack, plaintext_type: &Plaint // Return the size of the struct. Ok(size_of_name.saturating_add(size_of_members)) } + PlaintextType::ExternalStruct(locator) => { + let external_stack = stack.get_external_stack(locator.program_id())?; + plaintext_size_in_bytes(&*external_stack, &PlaintextType::Struct(*locator.resource())) + } PlaintextType::Array(array_type) => { // Retrieve the number of elements in the array. let num_elements = **array_type.length() as u64; @@ -548,7 +552,9 @@ pub fn cost_per_command( FinalizeType::Plaintext(PlaintextType::Literal(LiteralType::Field)) => Ok(1_500), FinalizeType::Plaintext(PlaintextType::Literal(_)) => Ok(500), FinalizeType::Plaintext(PlaintextType::Array(_)) => bail!("'div' does not support arrays"), - FinalizeType::Plaintext(PlaintextType::Struct(_)) => bail!("'div' does not support structs"), + FinalizeType::Plaintext(PlaintextType::Struct(_) | PlaintextType::ExternalStruct(_)) => { + bail!("'div' does not support structs") + } FinalizeType::Future(_) => bail!("'div' does not support futures"), } } @@ -762,7 +768,9 @@ pub fn cost_per_command( FinalizeType::Plaintext(PlaintextType::Literal(LiteralType::Scalar)) => Ok(10_000), FinalizeType::Plaintext(PlaintextType::Literal(_)) => Ok(500), FinalizeType::Plaintext(PlaintextType::Array(_)) => bail!("'mul' does not support arrays"), - FinalizeType::Plaintext(PlaintextType::Struct(_)) => bail!("'mul' does not support structs"), + FinalizeType::Plaintext(PlaintextType::Struct(_) | PlaintextType::ExternalStruct(_)) => { + bail!("'mul' does not support structs") + } FinalizeType::Future(_) => bail!("'mul' does not support futures"), } } @@ -780,7 +788,9 @@ pub fn cost_per_command( FinalizeType::Plaintext(PlaintextType::Literal(LiteralType::Field)) => Ok(1_500), FinalizeType::Plaintext(PlaintextType::Literal(_)) => Ok(500), FinalizeType::Plaintext(PlaintextType::Array(_)) => bail!("'pow' does not support arrays"), - FinalizeType::Plaintext(PlaintextType::Struct(_)) => bail!("'pow' does not support structs"), + FinalizeType::Plaintext(PlaintextType::Struct(_) | PlaintextType::ExternalStruct(_)) => { + bail!("'pow' does not support structs") + } FinalizeType::Future(_) => bail!("'pow' does not support futures"), } } diff --git a/synthesizer/process/src/lib.rs b/synthesizer/process/src/lib.rs index e725ac0585..da7c2f088d 100644 --- a/synthesizer/process/src/lib.rs +++ b/synthesizer/process/src/lib.rs @@ -51,6 +51,7 @@ use console::{ Literal, Locator, Plaintext, + PlaintextType, ProgramID, Record, Request, @@ -182,6 +183,42 @@ impl Process { } } } + + /// Ensure that the types referred to in this program's mappings exist. + pub fn mapping_types_exist(&self, program: &Program) -> Result<()> { + for mapping in program.mappings().values() { + self.plaintext_exists(mapping.key().plaintext_type(), program)?; + self.plaintext_exists(mapping.value().plaintext_type(), program)?; + } + Ok(()) + } + + // If `type_` is a struct or an array containing a struct, ensure the struct type exists. + fn plaintext_exists(&self, type_: &PlaintextType, program: &Program) -> Result<()> { + match type_ { + PlaintextType::Literal(..) => Ok(()), + PlaintextType::Struct(struct_name) => { + // Retrieve the struct from the program. + ensure!( + program.get_struct(struct_name).is_ok(), + "Struct '{struct_name}' in '{}' is not defined.", + program.id() + ); + Ok(()) + } + PlaintextType::ExternalStruct(locator) => { + let stack = self.get_stack(locator.program_id())?; + ensure!( + stack.program().get_struct(locator.resource()).is_ok(), + "Struct '{}' in '{}' is not defined.", + locator.resource(), + stack.program().id(), + ); + Ok(()) + } + PlaintextType::Array(array_type) => self.plaintext_exists(array_type.base_element_type(), program), + } + } } impl Process { diff --git a/synthesizer/process/src/stack/finalize_types/initialize.rs b/synthesizer/process/src/stack/finalize_types/initialize.rs index e31cd9faa0..8f9b502d7c 100644 --- a/synthesizer/process/src/stack/finalize_types/initialize.rs +++ b/synthesizer/process/src/stack/finalize_types/initialize.rs @@ -13,6 +13,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use snarkvm_synthesizer_program::types_equivalent; + use super::*; impl FinalizeTypes { @@ -152,15 +154,10 @@ impl FinalizeTypes { impl FinalizeTypes { /// Ensure the given input register is well-formed. - #[inline] fn check_input(&mut self, stack: &Stack, register: &Register, finalize_type: &FinalizeType) -> Result<()> { // Ensure the register type is defined in the program. match finalize_type { - FinalizeType::Plaintext(PlaintextType::Literal(..)) => (), - FinalizeType::Plaintext(PlaintextType::Struct(struct_name)) => { - RegisterTypes::check_struct(stack, struct_name)? - } - FinalizeType::Plaintext(PlaintextType::Array(array_type)) => RegisterTypes::check_array(stack, array_type)?, + FinalizeType::Plaintext(plaintext_type) => RegisterTypes::check_plaintext_type(stack, plaintext_type)?, FinalizeType::Future(locator) => { ensure!( stack.program().contains_import(locator.program_id()), @@ -173,8 +170,8 @@ impl FinalizeTypes { // Insert the input register. self.add_input(register.clone(), finalize_type.clone())?; - // Ensure the register type and the input type match. - if finalize_type != &self.get_type(stack, register)? { + // Ensure the register type and the input type are equivalent. + if !finalize_types_equivalent(stack, finalize_type, stack, &self.get_type(stack, register)?)? { bail!("Input '{register}' does not match the expected input register type.") } @@ -262,9 +259,9 @@ impl FinalizeTypes { // If the register is a future, throw an error. FinalizeType::Future(..) => bail!("A future cannot be used in a `branch` command"), }; - // Check that the operands have the same type. + // Check that the operands have equivalent types. ensure!( - first_type == second_type, + types_equivalent(stack, &first_type, stack, &second_type)?, "Command '{}' expects operands of the same type. Found operands of type '{}' and '{}'", Branch::::opcode(), first_type, @@ -328,8 +325,8 @@ impl FinalizeTypes { // If the register is a future, throw an error. FinalizeType::Future(..) => bail!("A future cannot be used as a key in a `contains` command"), }; - // Check that the key type in the mapping matches the key type in the instruction. - if *mapping_key_type != key_type { + // Check that the key type in the mapping is equivalent to the key type in the instruction. + if !types_equivalent(stack, mapping_key_type, stack, &key_type)? { bail!( "Key type in `contains` '{key_type}' does not match the key type in the mapping '{mapping_key_type}'." ) @@ -393,8 +390,8 @@ impl FinalizeTypes { // If the register is a future, throw an error. FinalizeType::Future(..) => bail!("A future cannot be used as a key in a `get` command"), }; - // Check that the key type in the mapping matches the key type in the instruction. - if *mapping_key_type != key_type { + // Check that the key type in the mapping is equivalent to the key type in the instruction. + if !types_equivalent(stack, mapping_key_type, stack, &key_type)? { bail!("Key type in `get` '{key_type}' does not match the key type in the mapping '{mapping_key_type}'.") } // Get the destination register. @@ -456,8 +453,8 @@ impl FinalizeTypes { // If the register is a future, throw an error. FinalizeType::Future(..) => bail!("A future cannot be used as a key in a `get.or_use` command"), }; - // Check that the key type in the mapping matches the key type. - if *mapping_key_type != key_type { + // Check that the key type in the mapping is equivalent to the key type. + if !types_equivalent(stack, mapping_key_type, stack, &key_type)? { bail!( "Key type in `get.or_use` '{key_type}' does not match the key type in the mapping '{mapping_key_type}'." ) @@ -469,8 +466,8 @@ impl FinalizeTypes { // If the register is a future, throw an error. FinalizeType::Future(..) => bail!("A default value cannot be a future"), }; - // Check that the value type in the mapping matches the default value type. - if mapping_value_type != &default_value_type { + // Check that the value type in the mapping is equivalent to the default value type. + if !types_equivalent(stack, mapping_value_type, stack, &default_value_type)? { bail!( "Default value type in `get.or_use` '{default_value_type}' does not match the value type in the mapping '{mapping_value_type}'." ) @@ -531,8 +528,8 @@ impl FinalizeTypes { // If the register is a future, throw an error. FinalizeType::Future(..) => bail!("A future cannot be used as a key in a `set` command"), }; - // Check that the key type in the mapping matches the key type. - if *mapping_key_type != key_type { + // Check that the key type in the mapping is equivalent the key type. + if !types_equivalent(stack, mapping_key_type, stack, &key_type)? { bail!("Key type in `set` '{key_type}' does not match the key type in the mapping '{mapping_key_type}'.") } // Retrieve the type of the value. @@ -542,8 +539,8 @@ impl FinalizeTypes { // If the register is a future, throw an error. FinalizeType::Future(..) => bail!("A future cannot be used as a value in a `set` command"), }; - // Check that the value type in the mapping matches the type of the value. - if mapping_value_type != &value_type { + // Check that the value type in the mapping is equivalent the type of the value. + if !types_equivalent(stack, mapping_value_type, stack, &value_type)? { bail!( "Value type in `set` '{value_type}' does not match the value type in the mapping '{mapping_value_type}'." ) @@ -570,8 +567,8 @@ impl FinalizeTypes { // If the register is a future, throw an error. FinalizeType::Future(..) => bail!("A future cannot be used as a key in a `remove` command"), }; - // Check that the key type in the mapping matches the key type. - if *mapping_key_type != key_type { + // Check that the key type in the mapping is equivalent the key type. + if !types_equivalent(stack, mapping_key_type, stack, &key_type)? { bail!("Key type in `remove` '{key_type}' does not match the key type in the mapping '{mapping_key_type}'.") } Ok(()) @@ -666,19 +663,27 @@ impl FinalizeTypes { | CastType::Plaintext(PlaintextType::Literal(..)) => { ensure!(instruction.operands().len() == 1, "Expected 1 operand."); } - CastType::Plaintext(PlaintextType::Struct(struct_name)) => { - // Ensure the struct name exists in the program. - if !stack.program().contains_struct(struct_name) { - bail!("Struct '{struct_name}' is not defined.") - } + CastType::Plaintext(plaintext @ PlaintextType::Struct(struct_name)) => { + // Ensure that the type is valid. + RegisterTypes::check_plaintext_type(stack, plaintext)?; // Retrieve the struct. let struct_ = stack.program().get_struct(struct_name)?; // Ensure the operand types match the struct. self.matches_struct(stack, instruction.operands(), struct_)?; } - CastType::Plaintext(PlaintextType::Array(array_type)) => { - // Ensure that the array type is valid. - RegisterTypes::check_array(stack, array_type)?; + CastType::Plaintext(plaintext @ PlaintextType::ExternalStruct(locator)) => { + // Ensure that the type is valid. + RegisterTypes::check_plaintext_type(stack, plaintext)?; + let external_stack = stack.get_external_stack(locator.program_id())?; + let struct_name = locator.resource(); + // Retrieve the struct. + let struct_ = external_stack.program().get_struct(struct_name)?; + // Ensure the operand types match the struct. + self.matches_struct(&*external_stack, instruction.operands(), struct_)?; + } + CastType::Plaintext(plaintext @ PlaintextType::Array(array_type)) => { + // Ensure that the type is valid. + RegisterTypes::check_plaintext_type(stack, plaintext)?; // Ensure the operand types match the element type. self.matches_array(stack, instruction.operands(), array_type)?; } diff --git a/synthesizer/process/src/stack/finalize_types/matches.rs b/synthesizer/process/src/stack/finalize_types/matches.rs index 67bdecf067..8a7175da95 100644 --- a/synthesizer/process/src/stack/finalize_types/matches.rs +++ b/synthesizer/process/src/stack/finalize_types/matches.rs @@ -45,6 +45,7 @@ impl FinalizeTypes { // Ensure the literal type matches the member type. Operand::Literal(literal) => { ensure!( + // No need to call `types_equivalent`, since it can't be a struct. &PlaintextType::Literal(literal.to_type()) == member_type, "Struct member '{struct_name}.{member_name}' expects a {member_type}, but found '{operand}' in the operand.", ) @@ -60,7 +61,7 @@ impl FinalizeTypes { }; // Ensure the register type matches the member type. ensure!( - &plaintext_type == member_type, + types_equivalent(stack, &plaintext_type, stack, member_type)?, "Struct member '{struct_name}.{member_name}' expects {member_type}, but found '{plaintext_type}' in the operand '{operand}'.", ) } @@ -79,6 +80,7 @@ impl FinalizeTypes { }; // Ensure the operand type matches the member type. ensure!( + // No need to call `types_equivalent`, since `program_ref_type` cannot be a struct. &program_ref_type == member_type, "Struct member '{struct_name}.{member_name}' expects {member_type}, but found '{program_ref_type}' in the operand '{operand}'.", ) @@ -120,6 +122,7 @@ impl FinalizeTypes { // Ensure the literal type matches the element type. Operand::Literal(literal) => { ensure!( + // No need to call `types_equivalent`, since it can't be a struct. &PlaintextType::Literal(literal.to_type()) == array_type.next_element_type(), "Array element expects {}, but found '{operand}' in the operand.", array_type.next_element_type() @@ -136,7 +139,7 @@ impl FinalizeTypes { }; // Ensure the register type matches the element type. ensure!( - &plaintext_type == array_type.next_element_type(), + types_equivalent(stack, &plaintext_type, stack, array_type.next_element_type())?, "Array element expects {}, but found '{plaintext_type}' in the operand '{operand}'.", array_type.next_element_type() ) @@ -154,6 +157,7 @@ impl FinalizeTypes { }; // Ensure the operand type matches the element type. ensure!( + // No need to call `types_equivalent`, since `program_ref_type` cannot be a struct. &program_ref_type == array_type.next_element_type(), "Array element expects {}, but found '{program_ref_type}' in the operand '{operand}'.", array_type.next_element_type() diff --git a/synthesizer/process/src/stack/finalize_types/mod.rs b/synthesizer/process/src/stack/finalize_types/mod.rs index e76db2ebb6..e7f8e99983 100644 --- a/synthesizer/process/src/stack/finalize_types/mod.rs +++ b/synthesizer/process/src/stack/finalize_types/mod.rs @@ -53,6 +53,7 @@ use snarkvm_synthesizer_program::{ Remove, Set, StackTrait, + types_equivalent, }; use indexmap::IndexMap; @@ -157,6 +158,17 @@ impl FinalizeTypes { None => bail!("'{identifier}' does not exist in struct '{struct_name}'"), } } + // Access the member on the path to output the register type. + (FinalizeType::Plaintext(PlaintextType::ExternalStruct(locator)), Access::Member(identifier)) => { + // Retrieve the member type from the struct and check that it exists. + let external_stack = stack.get_external_stack(locator.program_id())?; + match external_stack.program().get_struct(locator.resource())?.members().get(identifier) { + // Retrieve the member and update `finalize_type` for the next iteration. + Some(member_type) => finalize_type = FinalizeType::Plaintext(member_type.clone()), + // Halts if the member does not exist. + None => bail!("'{identifier}' does not exist in struct '{locator}'"), + } + } // Access the member on the path to output the register type and check that it is in bounds. (FinalizeType::Plaintext(PlaintextType::Array(array_type)), Access::Index(index)) => { match index < array_type.length() { @@ -193,7 +205,10 @@ impl FinalizeTypes { None => bail!("Index out of bounds"), } } - (FinalizeType::Plaintext(PlaintextType::Struct(..)), Access::Index(..)) + ( + FinalizeType::Plaintext(PlaintextType::Struct(..) | PlaintextType::ExternalStruct(..)), + Access::Index(..), + ) | (FinalizeType::Plaintext(PlaintextType::Array(..)), Access::Member(..)) | (FinalizeType::Future(..), Access::Member(..)) => { bail!("Invalid access `{access}`") @@ -205,3 +220,18 @@ impl FinalizeTypes { Ok(finalize_type) } } + +pub fn finalize_types_equivalent( + stack0: &impl StackTrait, + type0: &FinalizeType, + stack1: &impl StackTrait, + type1: &FinalizeType, +) -> Result { + match (type0, type1) { + (FinalizeType::Plaintext(plaintext0), FinalizeType::Plaintext(plaintext1)) => { + types_equivalent(stack0, plaintext0, stack1, plaintext1) + } + (FinalizeType::Future(future0), FinalizeType::Future(future1)) => Ok(future0 == future1), + _ => Ok(false), + } +} diff --git a/synthesizer/process/src/stack/helpers/sample.rs b/synthesizer/process/src/stack/helpers/sample.rs index 082e09b548..ab1f9f5bd4 100644 --- a/synthesizer/process/src/stack/helpers/sample.rs +++ b/synthesizer/process/src/stack/helpers/sample.rs @@ -140,6 +140,11 @@ impl Stack { Plaintext::Struct(members, Default::default()) } + PlaintextType::ExternalStruct(locator) => { + let external_stack = self.get_external_stack(locator.program_id())?; + let new_type = PlaintextType::Struct(*locator.resource()); + return external_stack.sample_plaintext_internal(&new_type, depth, rng); + } // Sample an array. PlaintextType::Array(array_type) => { // Sample each element of the array. diff --git a/synthesizer/process/src/stack/helpers/stack_trait.rs b/synthesizer/process/src/stack/helpers/stack_trait.rs index d5ed8cdf00..da8e4c6790 100644 --- a/synthesizer/process/src/stack/helpers/stack_trait.rs +++ b/synthesizer/process/src/stack/helpers/stack_trait.rs @@ -457,6 +457,11 @@ impl Stack { // If `plaintext` is an array, this is a mismatch. Plaintext::Array(..) => bail!("'{plaintext_type}' is invalid: expected literal, found array"), }, + PlaintextType::ExternalStruct(locator) => { + let external_stack = self.get_external_stack(locator.program_id())?; + let new_type = PlaintextType::Struct(*locator.resource()); + external_stack.matches_plaintext_internal(plaintext, &new_type, depth) + } PlaintextType::Struct(struct_name) => { // Ensure the struct name is valid. ensure!(!Program::is_reserved_keyword(struct_name), "Struct '{struct_name}' is reserved"); diff --git a/synthesizer/process/src/stack/mod.rs b/synthesizer/process/src/stack/mod.rs index 74c9681ac4..a51990f82c 100644 --- a/synthesizer/process/src/stack/mod.rs +++ b/synthesizer/process/src/stack/mod.rs @@ -332,6 +332,12 @@ impl Stack { } } + for mapping in self.program.mappings().values() { + // These calls make sure structs exist. + RegisterTypes::check_plaintext_type(self, mapping.key().plaintext_type())?; + RegisterTypes::check_plaintext_type(self, mapping.value().plaintext_type())?; + } + // Drop the locks since the types have been initialized. drop(constructor_types); drop(register_types); diff --git a/synthesizer/process/src/stack/register_types/initialize.rs b/synthesizer/process/src/stack/register_types/initialize.rs index ec01845831..9ab2a9a5d0 100644 --- a/synthesizer/process/src/stack/register_types/initialize.rs +++ b/synthesizer/process/src/stack/register_types/initialize.rs @@ -254,9 +254,7 @@ impl RegisterTypes { fn check_input(&mut self, stack: &Stack, register: &Register, register_type: &RegisterType) -> Result<()> { // Ensure the register type is defined in the program. match register_type { - RegisterType::Plaintext(PlaintextType::Literal(..)) => (), - RegisterType::Plaintext(PlaintextType::Struct(struct_name)) => Self::check_struct(stack, struct_name)?, - RegisterType::Plaintext(PlaintextType::Array(array_type)) => Self::check_array(stack, array_type)?, + RegisterType::Plaintext(plaintext_type) => Self::check_plaintext_type(stack, plaintext_type)?, RegisterType::Record(identifier) => { // Ensure the record type is defined in the program. if !stack.program().contains_record(identifier) { @@ -285,7 +283,6 @@ impl RegisterTypes { } /// Ensure the given output register is well-formed. - #[inline] fn check_output(&self, stack: &Stack, operand: &Operand, register_type: &RegisterType) -> Result<()> { match operand { // Inform the user the output operand is an input register, to ensure this is intended behavior. @@ -305,9 +302,7 @@ impl RegisterTypes { // Ensure the register type is defined in the program. match register_type { - RegisterType::Plaintext(PlaintextType::Literal(..)) => (), - RegisterType::Plaintext(PlaintextType::Struct(struct_name)) => Self::check_struct(stack, struct_name)?, - RegisterType::Plaintext(PlaintextType::Array(array_type)) => Self::check_array(stack, array_type)?, + RegisterType::Plaintext(plaintext_type) => Self::check_plaintext_type(stack, plaintext_type)?, RegisterType::Record(identifier) => { // Ensure the record type is defined in the program. if !stack.program().contains_record(identifier) { @@ -334,11 +329,10 @@ impl RegisterTypes { }; // Ensure the operand type and the output type match. - if *register_type != self.get_type_from_operand(stack, operand)? { + let operand_type = self.get_type_from_operand(stack, operand)?; + if !register_types_equivalent(stack, register_type, stack, &operand_type)? { bail!( - "Output '{operand}' does not match the expected output operand type: expected '{}', found '{}'", - self.get_type_from_operand(stack, operand)?, - register_type + "Output '{operand}' does not match the expected output operand type: expected '{operand_type}', found '{register_type}'", ) } Ok(()) @@ -380,7 +374,6 @@ impl RegisterTypes { /// Ensures the opcode is a valid opcode and corresponds to the correct instruction. /// This method is called when adding a new closure or function to the program. - #[inline] fn check_instruction_opcode( &self, stack: &Stack, @@ -512,19 +505,27 @@ impl RegisterTypes { | CastType::Plaintext(PlaintextType::Literal(..)) => { ensure!(instruction.operands().len() == 1, "Expected 1 operand."); } - CastType::Plaintext(PlaintextType::Struct(struct_name)) => { - // Ensure the struct name exists in the program. - if !stack.program().contains_struct(struct_name) { - bail!("Struct '{struct_name}' is not defined.") - } + CastType::Plaintext(plaintext @ PlaintextType::Struct(struct_name)) => { + // Ensure the type is valid. + Self::check_plaintext_type(stack, plaintext)?; // Retrieve the struct. let struct_ = stack.program().get_struct(struct_name)?; // Ensure the operand types match the struct. self.matches_struct(stack, instruction.operands(), struct_)?; } - CastType::Plaintext(PlaintextType::Array(array_type)) => { - // Ensure that the array type is valid. - RegisterTypes::check_array(stack, array_type)?; + CastType::Plaintext(plaintext @ PlaintextType::ExternalStruct(locator)) => { + // Ensure the type is valid. + Self::check_plaintext_type(stack, plaintext)?; + let external_stack = stack.get_external_stack(locator.program_id())?; + let struct_name = locator.resource(); + // Retrieve the struct. + let struct_ = external_stack.program().get_struct(struct_name)?; + // Ensure the operand types match the struct. + self.matches_struct(&*external_stack, instruction.operands(), struct_)?; + } + CastType::Plaintext(plaintext @ PlaintextType::Array(array_type)) => { + // Ensure the type is valid. + Self::check_plaintext_type(stack, plaintext)?; // Ensure the operand types match the element type. self.matches_array(stack, instruction.operands(), array_type)?; } @@ -647,34 +648,24 @@ impl RegisterTypes { // Ok(()) // } - /// Ensures the struct exists in the program, and recursively-checks for array members. - pub(crate) fn check_struct(stack: &Stack, struct_name: &Identifier) -> Result<()> { - // Retrieve the struct from the program. - let Ok(struct_) = stack.program().get_struct(struct_name) else { - bail!("Struct '{struct_name}' in '{}' is not defined.", stack.program_id()) - }; - - // If the struct contains arrays, ensure their base element types are defined in the program. - for member in struct_.members().values() { - match member { - PlaintextType::Literal(..) => (), - PlaintextType::Struct(struct_name) => Self::check_struct(stack, struct_name)?, - PlaintextType::Array(array_type) => Self::check_array(stack, array_type)?, + /// Ensure any struct referenced directly or otherwise exists. + pub fn check_plaintext_type(stack: &Stack, type_: &PlaintextType) -> Result<()> { + match type_ { + PlaintextType::Literal(..) => Ok(()), + PlaintextType::Struct(struct_name) => { + // Retrieve the struct from the program. + let Ok(struct_) = stack.program().get_struct(struct_name) else { + bail!("Struct '{struct_name}' in '{}' is not defined.", stack.program_id()) + }; + struct_.members().values().try_for_each(|member| Self::check_plaintext_type(stack, member)) } - } - Ok(()) - } - - /// Ensure the base element type of the array is defined in the program. - pub(crate) fn check_array(stack: &Stack, array_type: &ArrayType) -> Result<()> { - // If the base element type is a struct, check that it is defined in the program. - if let PlaintextType::Struct(struct_name) = array_type.base_element_type() { - // Ensure the struct is defined in the program. - if !stack.program().contains_struct(struct_name) { - bail!("Struct '{struct_name}' in '{}' is not defined.", stack.program_id()) + PlaintextType::ExternalStruct(locator) => { + let external_stack = stack.get_external_stack(locator.program_id())?; + let struct_type = PlaintextType::Struct(*locator.resource()); + Self::check_plaintext_type(&*external_stack, &struct_type) } + PlaintextType::Array(array_type) => Self::check_plaintext_type(stack, array_type.base_element_type()), } - Ok(()) } /// Ensures the opcode is a valid opcode and corresponds to the `commit` instruction. diff --git a/synthesizer/process/src/stack/register_types/matches.rs b/synthesizer/process/src/stack/register_types/matches.rs index 7dace1e094..b69d078e57 100644 --- a/synthesizer/process/src/stack/register_types/matches.rs +++ b/synthesizer/process/src/stack/register_types/matches.rs @@ -64,7 +64,7 @@ impl RegisterTypes { // Ensure the register type matches the member type. RegisterType::Plaintext(type_) => { ensure!( - &type_ == member_type, + types_equivalent(stack, &type_, stack, member_type)?, "Struct entry '{struct_name}.{member_name}' expects a '{member_type}', but found '{type_}' in the operand '{operand}'.", ) } @@ -80,7 +80,7 @@ impl RegisterTypes { }; // Ensure the operand type matches the member type. ensure!( - &operand_type == member_type, + types_equivalent(stack, &operand_type, stack, member_type)?, "Struct member '{struct_name}.{member_name}' expects {member_type}, but found '{operand_type}' in the operand '{operand}'.", ) } @@ -159,7 +159,7 @@ impl RegisterTypes { // Ensure the register type matches the element type. RegisterType::Plaintext(type_) => { ensure!( - &type_ == array_type.next_element_type(), + types_equivalent(stack, &type_, stack, array_type.next_element_type())?, "Array element expects a '{}', but found '{type_}' in the operand '{operand}'.", array_type.next_element_type() ) @@ -174,7 +174,7 @@ impl RegisterTypes { }; // Ensure the operand type matches the element type. ensure!( - &operand_type == array_type.next_element_type(), + types_equivalent(stack, &operand_type, stack, array_type.next_element_type())?, "Array element expects {}, but found '{operand_type}' in the operand '{operand}'.", array_type.next_element_type() ) @@ -297,7 +297,7 @@ impl RegisterTypes { // Ensure the register type matches the entry type. RegisterType::Plaintext(type_) => { ensure!( - &type_ == plaintext_type, + types_equivalent(stack, &type_, stack, plaintext_type)?, "Record entry '{record_name}.{entry_name}' expects a '{plaintext_type}', but found '{type_}' in the operand '{operand}'.", ) } @@ -314,7 +314,7 @@ impl RegisterTypes { }; // Ensure the operand type matches the entry type. ensure!( - &operand_type == plaintext_type, + types_equivalent(stack, &operand_type, stack, plaintext_type)?, "Record entry '{record_name}.{entry_name}' expects a '{plaintext_type}', but found '{operand_type}' in the operand '{operand}'.", ) } diff --git a/synthesizer/process/src/stack/register_types/mod.rs b/synthesizer/process/src/stack/register_types/mod.rs index d958f8543b..74c8999ad7 100644 --- a/synthesizer/process/src/stack/register_types/mod.rs +++ b/synthesizer/process/src/stack/register_types/mod.rs @@ -46,12 +46,14 @@ use snarkvm_synthesizer_program::{ Operand, Program, StackTrait, + register_types_equivalent, + types_equivalent, }; use snarkvm_utilities::dev_eprintln; use indexmap::{IndexMap, IndexSet}; -#[derive(Clone, Default, PartialEq, Eq)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct RegisterTypes { /// The mapping of all input registers to their defined types. inputs: IndexMap>, @@ -218,6 +220,15 @@ impl RegisterTypes { None => bail!("'{identifier}' does not exist in struct '{struct_name}'"), } } + (RegisterAccessType::Plaintext(PlaintextType::ExternalStruct(locator)), Access::Member(identifier)) => { + let external_stack = stack.get_external_stack(locator.program_id())?; + // Retrieve the member type from the struct. + match external_stack.program().get_struct(locator.resource())?.members().get(identifier) { + // Update the member type. + Some(member_type) => register_type = RegisterAccessType::Plaintext(member_type.clone()), + None => bail!("'{identifier}' does not exist in struct '{locator}'"), + } + } // Traverse the path to output the register type. (RegisterAccessType::Plaintext(PlaintextType::Array(array_type)), Access::Index(index)) => { match index < array_type.length() { @@ -259,7 +270,10 @@ impl RegisterTypes { None => bail!("Index out of bounds"), } } - (RegisterAccessType::Plaintext(PlaintextType::Struct(..)), Access::Index(..)) + ( + RegisterAccessType::Plaintext(PlaintextType::Struct(..) | PlaintextType::ExternalStruct(..)), + Access::Index(..), + ) | (RegisterAccessType::Plaintext(PlaintextType::Array(..)), Access::Member(..)) | (RegisterAccessType::Future(..), Access::Member(..)) => { bail!("Invalid access `{access}`") diff --git a/synthesizer/process/src/tests/test_serializers.rs b/synthesizer/process/src/tests/test_serializers.rs index f5adeb862c..6aa75f0c0d 100644 --- a/synthesizer/process/src/tests/test_serializers.rs +++ b/synthesizer/process/src/tests/test_serializers.rs @@ -101,11 +101,12 @@ function test_serde_equivalence: // Structs are not supported. let fail_get_struct = |_: &Identifier| bail!("structs are not supported"); + let fail_get_external_struct = |_: &Locator| bail!("structs are not supported"); // Get the bits type. let num_bits = match is_raw { - true => type_.size_in_bits_raw(&fail_get_struct).unwrap(), - false => type_.size_in_bits(&fail_get_struct).unwrap(), + true => type_.size_in_bits_raw(&fail_get_struct, &fail_get_external_struct).unwrap(), + false => type_.size_in_bits(&fail_get_struct, &fail_get_external_struct).unwrap(), }; let num_bits = u32::try_from(num_bits).unwrap(); let bits_type = ArrayType::new(PlaintextType::Literal(LiteralType::Boolean), vec![U32::new(num_bits)]).unwrap(); @@ -191,10 +192,53 @@ fn test_value_size_in_bits() { // Load a process. let mut process = Process::::load().unwrap(); + // Define a program . + let program0 = Program::::from_str( + r" +program test0.aleo; + +struct A: + one as field; + two as u8; + three as signature; + +struct B: + one as [scalar; 32u32]; + two as [A; 4u32]; + +record credits: + owner as address.private; + microcredits as u64.private; + +record C: + owner as address.private; + data as [u8; 16u32].private; + amount as u32.private; + ayyy as A.private; + bees as [B; 2u32].private; + +function dummy: + input r0 as A.public; + input r1 as B.public; + async dummy r0 r1 into r2; + output r2 as test0.aleo/dummy.future; +finalize dummy: + input r0 as A.public; + input r1 as B.public; + assert.eq r0.one r1.two[0u32].one; + ", + ) + .unwrap(); + + // Add the program to the process. + process.add_program(&program0).unwrap(); + // Define a program with data types that we want to test. - let program = Program::::from_str( + let program1 = Program::::from_str( r" -program test.aleo; +import test0.aleo; + +program test1.aleo; struct A: one as field; @@ -220,7 +264,7 @@ function dummy: input r0 as A.public; input r1 as B.public; async dummy r0 r1 into r2; - output r2 as test.aleo/dummy.future; + output r2 as test1.aleo/dummy.future; finalize dummy: input r0 as A.public; input r1 as B.public; @@ -230,19 +274,26 @@ finalize dummy: .unwrap(); // Add the program to the process. - process.add_program(&program).unwrap(); + process.add_program(&program1).unwrap(); // Get the stack. - let stack = process.get_stack(program.id()).unwrap(); + let stack = process.get_stack(program1.id()).unwrap(); - // A helper function to get the struct. + // A helper function to get a struct declaration. let get_struct = |id: &Identifier| stack.program().get_struct(id).cloned(); + // A helper function to get an external struct declaration. + let get_external_struct = |locator: &Locator| { + stack.get_external_stack(locator.program_id())?.program().get_struct(locator.resource()).cloned() + }; + // A helper to get a record declaration. let get_record = |identifier: &Identifier| stack.program().get_record(identifier).cloned(); // A helper to get an external record declaration. - let get_external_record = |_locator: &Locator| unimplemented!("Not tested"); + let get_external_record = |locator: &Locator| { + stack.get_external_stack(locator.program_id())?.program().get_record(locator.resource()).cloned() + }; // A helper to get the argument types of a future. let get_future = |locator: &Locator| { @@ -292,15 +343,31 @@ finalize dummy: )), RegisterType::Plaintext(PlaintextType::Struct(Identifier::from_str("A").unwrap())), RegisterType::Plaintext(PlaintextType::Struct(Identifier::from_str("B").unwrap())), + RegisterType::Plaintext(PlaintextType::ExternalStruct(Locator::from_str("test0.aleo/A").unwrap())), + RegisterType::Plaintext(PlaintextType::ExternalStruct(Locator::from_str("test0.aleo/B").unwrap())), RegisterType::Plaintext(PlaintextType::Array( ArrayType::new(PlaintextType::Struct(Identifier::from_str("A").unwrap()), vec![U32::new(3)]).unwrap(), )), RegisterType::Plaintext(PlaintextType::Array( ArrayType::new(PlaintextType::Struct(Identifier::from_str("B").unwrap()), vec![U32::new(2)]).unwrap(), )), + RegisterType::Plaintext(PlaintextType::Array( + ArrayType::new(PlaintextType::ExternalStruct(Locator::from_str("test0.aleo/A").unwrap()), vec![U32::new( + 3, + )]) + .unwrap(), + )), + RegisterType::Plaintext(PlaintextType::Array( + ArrayType::new(PlaintextType::ExternalStruct(Locator::from_str("test0.aleo/B").unwrap()), vec![U32::new( + 2, + )]) + .unwrap(), + )), RegisterType::Record(Identifier::from_str("credits").unwrap()), RegisterType::Record(Identifier::from_str("C").unwrap()), - RegisterType::Future(Locator::from_str("test.aleo/dummy").unwrap()), + RegisterType::ExternalRecord(Locator::from_str("test0.aleo/credits").unwrap()), + RegisterType::ExternalRecord(Locator::from_str("test0.aleo/C").unwrap()), + RegisterType::Future(Locator::from_str("test1.aleo/dummy").unwrap()), ]; for is_raw in [false, true] { @@ -313,10 +380,18 @@ finalize dummy: // Get the size in bits. let size_in_bits = match is_raw { - true => { - type_.size_in_bits_raw(&get_struct, &get_record, &get_external_record, &get_future).unwrap() - } - false => type_.size_in_bits(&get_struct, &get_record, &get_external_record, &get_future).unwrap(), + true => type_ + .size_in_bits_raw( + &get_struct, + &get_external_struct, + &get_record, + &get_external_record, + &get_future, + ) + .unwrap(), + false => type_ + .size_in_bits(&get_struct, &get_external_struct, &get_record, &get_external_record, &get_future) + .unwrap(), }; // Sample the value. @@ -329,7 +404,9 @@ finalize dummy: }; // Check that the number of bits matches the expected size. - println!("Expected size in bits: {size_in_bits}, Actual size in bits: {}", bits.len()); + if bits.len() != size_in_bits { + println!("Expected size in bits: {size_in_bits}, Actual size in bits: {}", bits.len()); + } assert_eq!(bits.len(), size_in_bits); } }) diff --git a/synthesizer/program/src/closure/mod.rs b/synthesizer/program/src/closure/mod.rs index db553f0936..c7c05eda40 100644 --- a/synthesizer/program/src/closure/mod.rs +++ b/synthesizer/program/src/closure/mod.rs @@ -70,6 +70,18 @@ impl ClosureCore { &self.outputs } + /// Returns the closure output types. + pub fn output_types(&self) -> Vec> { + self.outputs.iter().map(|output| output.register_type()).cloned().collect() + } + + /// Returns whether the closure refers to an external struct. + pub fn contains_external_struct(&self) -> bool { + self.inputs.iter().any(|input| input.register_type().contains_external_struct()) + || self.outputs.iter().any(|output| output.register_type().contains_external_struct()) + || self.instructions.iter().any(|instruction| instruction.contains_external_struct()) + } + /// Returns `true` if the closure instructions contain a string type. /// Note that input and output types don't have to be checked if we are sure the broader function doesn't contain a string type. pub fn contains_string_type(&self) -> bool { diff --git a/synthesizer/program/src/constructor/mod.rs b/synthesizer/program/src/constructor/mod.rs index 08b5547bda..482740a6be 100644 --- a/synthesizer/program/src/constructor/mod.rs +++ b/synthesizer/program/src/constructor/mod.rs @@ -51,6 +51,16 @@ impl ConstructorCore { &self.positions } + /// Returns whether this constructor refers to an external struct. + pub fn contains_external_struct(&self) -> bool { + self.commands.iter().any(|command| { + matches!( + command, + Command::Instruction(instruction) if instruction.contains_external_struct() + ) + }) + } + /// Returns `true` if the constructor commands contain a string type. pub fn contains_string_type(&self) -> bool { self.commands.iter().any(|command| command.contains_string_type()) diff --git a/synthesizer/program/src/finalize/mod.rs b/synthesizer/program/src/finalize/mod.rs index bf35629a03..52f7793a09 100644 --- a/synthesizer/program/src/finalize/mod.rs +++ b/synthesizer/program/src/finalize/mod.rs @@ -80,6 +80,12 @@ impl FinalizeCore { &self.positions } + pub fn contains_external_struct(&self) -> bool { + self.commands + .iter() + .any(|command| matches!(command, Command::Instruction(inst) if inst.contains_external_struct())) + } + /// Returns `true` if the finalize scope contains a string type. pub fn contains_string_type(&self) -> bool { self.input_types().iter().any(|input_type| { diff --git a/synthesizer/program/src/function/mod.rs b/synthesizer/program/src/function/mod.rs index 2e6f3a2102..51dc638e7c 100644 --- a/synthesizer/program/src/function/mod.rs +++ b/synthesizer/program/src/function/mod.rs @@ -96,6 +96,14 @@ impl FunctionCore { self.finalize_logic.as_ref() } + /// Returns whether this function refers to an external struct. + pub fn contains_external_struct(&self) -> bool { + self.inputs.iter().any(|input| input.value_type().contains_external_struct()) + || self.outputs.iter().any(|output| output.value_type().contains_external_struct()) + || self.instructions.iter().any(|instruction| instruction.contains_external_struct()) + || self.finalize_logic.iter().any(|finalize| finalize.contains_external_struct()) + } + /// Returns `true` if the function contains a string type. pub fn contains_string_type(&self) -> bool { self.input_types().iter().any(|input| input.contains_string_type()) diff --git a/synthesizer/program/src/lib.rs b/synthesizer/program/src/lib.rs index 9f3bb4fffe..0dd597c820 100644 --- a/synthesizer/program/src/lib.rs +++ b/synthesizer/program/src/lib.rs @@ -566,12 +566,32 @@ impl ProgramCore { bail!("'{member_identifier}' in struct '{struct_name}' is not defined.") } } + PlaintextType::ExternalStruct(locator) => { + if !self.imports.contains_key(locator.program_id()) { + bail!( + "External program {} referenced in struct '{struct_name}' does not exist", + locator.program_id() + ); + } + } PlaintextType::Array(array_type) => { - if let PlaintextType::Struct(struct_name) = array_type.base_element_type() { + match array_type.base_element_type() { + PlaintextType::Struct(struct_name) => // Ensure the member struct name exists in the program. - if !self.structs.contains_key(struct_name) { - bail!("'{struct_name}' in array '{array_type}' is not defined.") + { + if !self.structs.contains_key(struct_name) { + bail!("'{struct_name}' in array '{array_type}' is not defined.") + } } + PlaintextType::ExternalStruct(locator) => { + if !self.imports.contains_key(locator.program_id()) { + bail!( + "External program {} in array '{array_type}' does not exist", + locator.program_id() + ); + } + } + PlaintextType::Array(..) | PlaintextType::Literal(..) => {} } } } @@ -623,12 +643,32 @@ impl ProgramCore { bail!("Struct '{identifier}' in record '{record_name}' is not defined.") } } + PlaintextType::ExternalStruct(locator) => { + if !self.imports.contains_key(locator.program_id()) { + bail!( + "External program {} referenced in record '{record_name}' does not exist", + locator.program_id() + ); + } + } PlaintextType::Array(array_type) => { - if let PlaintextType::Struct(struct_name) = array_type.base_element_type() { + match array_type.base_element_type() { + PlaintextType::Struct(struct_name) => // Ensure the member struct name exists in the program. - if !self.structs.contains_key(struct_name) { - bail!("'{struct_name}' in array '{array_type}' is not defined.") + { + if !self.structs.contains_key(struct_name) { + bail!("'{struct_name}' in array '{array_type}' is not defined.") + } + } + PlaintextType::ExternalStruct(locator) => { + if !self.imports.contains_key(locator.program_id()) { + bail!( + "External program {} in array '{array_type}' does not exist", + locator.program_id() + ); + } } + PlaintextType::Array(..) | PlaintextType::Literal(..) => {} } } } @@ -913,6 +953,27 @@ impl ProgramCore { false } + /// Returns whether this program explicitly refers to an external struct, like `other_program.aleo/StructType`? + /// + /// This function exists to check if programs to be deployed use external structs so they can be gated + /// by consensus version. + pub fn contains_external_struct(&self) -> bool { + self.mappings.values().any(|mapping| mapping.contains_external_struct()) + || self + .structs + .values() + .flat_map(|struct_| struct_.members().values()) + .any(|plaintext_type| plaintext_type.contains_external_struct()) + || self + .records + .values() + .flat_map(|record| record.entries().values()) + .any(|entry| entry.plaintext_type().contains_external_struct()) + || self.closures.values().any(|closure| closure.contains_external_struct()) + || self.functions.values().any(|function| function.contains_external_struct()) + || self.constructor.iter().any(|constructor| constructor.contains_external_struct()) + } + /// Returns `true` if the program contains an array type with a size that exceeds the given maximum. pub fn exceeds_max_array_size(&self, max_array_size: u32) -> bool { self.mappings.values().any(|mapping| mapping.exceeds_max_array_size(max_array_size)) diff --git a/synthesizer/program/src/logic/instruction/mod.rs b/synthesizer/program/src/logic/instruction/mod.rs index b2ec7a510c..1721b442f5 100644 --- a/synthesizer/program/src/logic/instruction/mod.rs +++ b/synthesizer/program/src/logic/instruction/mod.rs @@ -589,6 +589,16 @@ impl Instruction { instruction!(self, |instruction| instruction.output_types(stack, input_types)) } + /// Returns whether this instruction refers to an external struct. + pub fn contains_external_struct(&self) -> bool { + // Only cast instructions may contain an explicit reference to an external struct. + // Calls may produce them, but they don't explicitly reference the type, and that's + // always been allowed. + matches!(self, + Instruction::Cast(instruction) if instruction.cast_type().contains_external_struct() + ) + } + /// Returns `true` if the instruction contains a literal string type. pub fn contains_string_type(&self) -> bool { self.operands().iter().any(|operand| operand.contains_string_type()) diff --git a/synthesizer/program/src/logic/instruction/operation/assert.rs b/synthesizer/program/src/logic/instruction/operation/assert.rs index 38a35cc2eb..f887fe9fb7 100644 --- a/synthesizer/program/src/logic/instruction/operation/assert.rs +++ b/synthesizer/program/src/logic/instruction/operation/assert.rs @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{Opcode, Operand, RegistersCircuit, RegistersTrait, StackTrait}; +use crate::{Opcode, Operand, RegistersCircuit, RegistersTrait, StackTrait, register_types_equivalent}; use console::{ network::prelude::*, program::{Register, RegisterType}, @@ -134,17 +134,17 @@ impl AssertInstruction { /// Returns the output type from the given program and input types. pub fn output_types( &self, - _stack: &impl StackTrait, + stack: &impl StackTrait, input_types: &[RegisterType], ) -> Result>> { // Ensure the number of input types is correct. if input_types.len() != 2 { bail!("Instruction '{}' expects 2 inputs, found {} inputs", Self::opcode(), input_types.len()) } - // Ensure the operands are of the same type. - if input_types[0] != input_types[1] { + // Ensure the operands have equivalent types. + if !register_types_equivalent(stack, &input_types[0], stack, &input_types[1])? { bail!( - "Instruction '{}' expects inputs of the same type. Found inputs of type '{}' and '{}'", + "Instruction '{}' expects inputs of equivalent types. Found inputs of type '{}' and '{}'", Self::opcode(), input_types[0], input_types[1] diff --git a/synthesizer/program/src/logic/instruction/operation/async_.rs b/synthesizer/program/src/logic/instruction/operation/async_.rs index 8c92c8704c..113bc8e564 100644 --- a/synthesizer/program/src/logic/instruction/operation/async_.rs +++ b/synthesizer/program/src/logic/instruction/operation/async_.rs @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{Opcode, Operand, RegistersCircuit, RegistersTrait, Result, StackTrait}; +use crate::{Opcode, Operand, RegistersCircuit, RegistersTrait, Result, StackTrait, types_equivalent}; use circuit::{Inject, Mode}; use console::{ @@ -159,7 +159,7 @@ impl Async { match (input_type, finalize_type) { (RegisterType::Plaintext(input_type), FinalizeType::Plaintext(finalize_type)) => { ensure!( - input_type == &finalize_type, + types_equivalent(stack, input_type, stack, &finalize_type)?, "'{}/{}' finalize expects a '{}' argument, found a '{}' argument", stack.program_id(), self.function_name(), diff --git a/synthesizer/program/src/logic/instruction/operation/call.rs b/synthesizer/program/src/logic/instruction/operation/call.rs index 694dddb970..45401373ff 100644 --- a/synthesizer/program/src/logic/instruction/operation/call.rs +++ b/synthesizer/program/src/logic/instruction/operation/call.rs @@ -16,7 +16,7 @@ use crate::{Opcode, Operand, RegistersCircuit, RegistersTrait, StackTrait}; use console::{ network::prelude::*, - program::{Identifier, Locator, Register, RegisterType, ValueType}, + program::{Identifier, Locator, Register, RegisterType}, }; /// The operator references a function name or closure name. @@ -181,16 +181,18 @@ impl Call { } /// Returns the output type from the given program and input types. - #[inline] pub fn output_types( &self, stack: &impl StackTrait, input_types: &[RegisterType], ) -> Result>> { - // Retrieve the external stack, if needed, and the resource. - let (external_stack, resource) = match &self.operator { + // Retrieve the program (external if necessary) and the name of the function or closure. + let stack_value; + let (is_external, program, name) = match &self.operator { CallOperator::Locator(locator) => { - (Some(stack.get_external_stack(locator.program_id())?), locator.resource()) + let program_name = locator.program_id(); + stack_value = Some(stack.get_external_stack(program_name)?); + (true, stack_value.as_ref().unwrap().program(), locator.resource()) } CallOperator::Resource(resource) => { // TODO (howardwu): Revisit this decision to forbid calling internal functions. A record cannot be spent again. @@ -199,16 +201,12 @@ impl Call { if stack.program().contains_function(resource) { bail!("Cannot call '{resource}'. Use a closure ('closure {resource}:') instead.") } - (None, resource) + (false, stack.program(), resource) } }; - // Retrieve the program. - let (is_external, program) = match &external_stack { - Some(external_stack) => (true, external_stack.program()), - None => (false, stack.program()), - }; + // If the operator is a closure, retrieve the closure and compute the output types. - if let Ok(closure) = program.get_closure(resource) { + if let Ok(closure) = program.get_closure(name) { // Ensure the number of operands matches the number of input statements. if closure.inputs().len() != self.operands.len() { bail!("Expected {} inputs, found {}", closure.inputs().len(), self.operands.len()) @@ -222,10 +220,16 @@ impl Call { bail!("Expected {} outputs, found {}", closure.outputs().len(), self.destinations.len()) } // Return the output register types. - Ok(closure.outputs().iter().map(|output| output.register_type()).cloned().collect()) + Ok(closure + .output_types() + .into_iter() + // If the function is an external program, we need to qualify its structs with + // the appropriate `ProgramID`. + .map(|output_type| if is_external { output_type.qualify(*program.id()) } else { output_type }) + .collect::>()) } // If the operator is a function, retrieve the function and compute the output types. - else if let Ok(function) = program.get_function(resource) { + else if let Ok(function) = program.get_function(name) { // Ensure the number of operands matches the number of input statements. if function.inputs().len() != self.operands.len() { bail!("Expected {} inputs, found {}", function.inputs().len(), self.operands.len()) @@ -239,18 +243,14 @@ impl Call { bail!("Expected {} outputs, found {}", function.outputs().len(), self.destinations.len()) } // Return the output register types. - function + Ok(function .output_types() .into_iter() - .map(|output_type| match (is_external, output_type) { - // If the output is a record and the function is external, return the external record type. - (true, ValueType::Record(record_name)) => Ok(RegisterType::ExternalRecord(Locator::from_str( - &format!("{}/{}", program.id(), record_name), - )?)), - // Else, return the register type. - (_, output_type) => Ok(RegisterType::from(output_type)), - }) - .collect::>>() + .map(RegisterType::from) + // If the function is an external program, we need to qualify its structs or records with + // the appropriate `ProgramID`. + .map(|register_type| if is_external { register_type.qualify(*program.id()) } else { register_type }) + .collect::>()) } // Else, throw an error. else { diff --git a/synthesizer/program/src/logic/instruction/operation/cast.rs b/synthesizer/program/src/logic/instruction/operation/cast.rs index 0e81a55036..7f3ba6c476 100644 --- a/synthesizer/program/src/logic/instruction/operation/cast.rs +++ b/synthesizer/program/src/logic/instruction/operation/cast.rs @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{Opcode, Operand, RegistersCircuit, RegistersSigner, RegistersTrait, StackTrait}; +use crate::{Opcode, Operand, RegistersCircuit, RegistersSigner, RegistersTrait, StackTrait, types_equivalent}; use console::{ network::prelude::*, program::{ @@ -30,6 +30,7 @@ use console::{ Record, Register, RegisterType, + StructType, Value, ValueType, }, @@ -142,6 +143,13 @@ impl FromBytes for CastType { } } +impl CastType { + /// Returns whether this type refers to an external struct. + pub fn contains_external_struct(&self) -> bool { + matches!(self, CastType::Plaintext(plaintext_type) if plaintext_type.contains_external_struct()) + } +} + /// The `cast` instruction. pub type Cast = CastOperation; /// The `cast.lossy` instruction. @@ -238,7 +246,13 @@ impl CastOperation { registers.store(stack, &self.destination, Value::Plaintext(Plaintext::from(value))) } CastType::Plaintext(PlaintextType::Struct(struct_name)) => { - self.cast_to_struct(stack, registers, *struct_name, inputs) + let plaintext = self.evaluate_cast_to_struct(stack, *struct_name, inputs)?; + registers.store(stack, &self.destination, plaintext.into()) + } + CastType::Plaintext(PlaintextType::ExternalStruct(locator)) => { + let external_stack = stack.get_external_stack(locator.program_id())?; + let plaintext = self.evaluate_cast_to_struct(&*external_stack, *locator.resource(), inputs)?; + registers.store(stack, &self.destination, plaintext.into()) } CastType::Plaintext(PlaintextType::Array(array_type)) => { self.cast_to_array(stack, registers, array_type, inputs) @@ -389,57 +403,16 @@ impl CastOperation { circuit::Value::Plaintext(circuit::Plaintext::from(value)), ) } - CastType::Plaintext(PlaintextType::Struct(struct_)) => { - // Ensure the operands length is at least the minimum. - if inputs.len() < N::MIN_STRUCT_ENTRIES { - bail!("Casting to a struct requires at least {} operand(s)", N::MIN_STRUCT_ENTRIES) - } - // Ensure the number of members does not exceed the maximum. - if inputs.len() > N::MAX_STRUCT_ENTRIES { - bail!("Casting to struct '{struct_}' cannot exceed {} members", N::MAX_STRUCT_ENTRIES) - } - - // Retrieve the struct and ensure it is defined in the program. - let struct_ = stack.program().get_struct(struct_)?; - - // Ensure that the number of operands is equal to the number of struct members. - if inputs.len() != struct_.members().len() { - bail!( - "Casting to the struct {} requires {} operands, but {} were provided", - struct_.name(), - struct_.members().len(), - inputs.len() - ) - } - - // Initialize the struct members. - let mut members = IndexMap::new(); - for (member, (member_name, member_type)) in inputs.iter().zip_eq(struct_.members()) { - // Retrieve the plaintext value from the entry. - let plaintext = match member { - circuit::Value::Plaintext(plaintext) => { - // Ensure the member matches the register type. - stack.matches_plaintext(&plaintext.eject_value(), member_type)?; - // Output the plaintext. - plaintext.clone() - } - // Ensure the struct member is not a record. - circuit::Value::Record(..) => { - bail!("Casting a record into a struct member is illegal") - } - // Ensure the struct member is not a future. - circuit::Value::Future(..) => { - bail!("Casting a future into a struct member is illegal") - } - }; - // Append the member to the struct members. - members.insert(circuit::Identifier::constant(*member_name), plaintext); - } - - // Construct the struct. - let struct_ = circuit::Plaintext::Struct(members, Default::default()); + CastType::Plaintext(PlaintextType::ExternalStruct(locator)) => { + let external_stack = stack.get_external_stack(locator.program_id())?; + let plaintext = self.execute_cast_to_struct(&*external_stack, *locator.resource(), inputs)?; + // Store the struct. + registers.store_circuit(stack, &self.destination, plaintext.into()) + } + CastType::Plaintext(PlaintextType::Struct(struct_name)) => { + let plaintext = self.execute_cast_to_struct(stack, *struct_name, inputs)?; // Store the struct. - registers.store_circuit(stack, &self.destination, circuit::Value::Plaintext(struct_)) + registers.store_circuit(stack, &self.destination, plaintext.into()) } CastType::Plaintext(PlaintextType::Array(array_type)) => { // Ensure the operands length is at least the minimum. @@ -626,7 +599,13 @@ impl CastOperation { registers.store(stack, &self.destination, Value::Plaintext(Plaintext::from(value))) } CastType::Plaintext(PlaintextType::Struct(struct_name)) => { - self.cast_to_struct(stack, registers, *struct_name, inputs) + let plaintext = self.evaluate_cast_to_struct(stack, *struct_name, inputs)?; + registers.store(stack, &self.destination, plaintext.into()) + } + CastType::Plaintext(PlaintextType::ExternalStruct(locator)) => { + let external_stack = stack.get_external_stack(locator.program_id())?; + let plaintext = self.evaluate_cast_to_struct(&*external_stack, *locator.resource(), inputs)?; + registers.store(stack, &self.destination, plaintext.into()) } CastType::Plaintext(PlaintextType::Array(array_type)) => { self.cast_to_array(stack, registers, array_type, inputs) @@ -663,6 +642,54 @@ impl CastOperation { self.operands.len(), ); + let struct_checks = |struct_type: &StructType| { + let struct_name = struct_type.name(); + + // Ensure the input types length is at least the minimum. + if input_types.len() < N::MIN_STRUCT_ENTRIES { + bail!("Casting to a struct requires at least {} operand(s)", N::MIN_STRUCT_ENTRIES) + } + // Ensure the number of members does not exceed the maximum. + if input_types.len() > N::MAX_STRUCT_ENTRIES { + bail!("Casting to struct '{struct_type}' cannot exceed {} members", N::MAX_STRUCT_ENTRIES) + } + + // Ensure that the number of input types is equal to the number of struct members. + ensure!( + input_types.len() == struct_type.members().len(), + "Casting to the struct {} requires {} operands, but {} were provided", + struct_type.name(), + struct_type.members().len(), + input_types.len() + ); + // Ensure the input types match the struct. + for ((_, member_type), input_type) in struct_type.members().iter().zip_eq(input_types) { + match input_type { + // Ensure the plaintext type matches the member type. + RegisterType::Plaintext(plaintext_type) => { + ensure!( + types_equivalent(stack, member_type, stack, plaintext_type,)?, + "Struct '{struct_name}' member type mismatch: expected '{member_type}', found '{plaintext_type}'" + ) + } + // Ensure the input type cannot be a record (this is unsupported behavior). + RegisterType::Record(record_name) => bail!( + "Struct '{struct_name}' member type mismatch: expected '{member_type}', found record '{record_name}'" + ), + // Ensure the input type cannot be an external record (this is unsupported behavior). + RegisterType::ExternalRecord(locator) => bail!( + "Struct '{struct_name}' member type mismatch: expected '{member_type}', found external record '{locator}'" + ), + // Ensure the input type cannot be a future (this is unsupported behavior). + RegisterType::Future(..) => { + bail!("Struct '{struct_name}' member type mismatch: expected '{member_type}', found future") + } + } + } + + Ok(()) + }; + // Ensure the output type is defined in the program. match &self.cast_type { CastType::GroupXCoordinate | CastType::GroupYCoordinate => { @@ -676,51 +703,15 @@ impl CastOperation { CastType::Plaintext(PlaintextType::Literal(..)) => { ensure!(input_types.len() == 1, "Casting to a literal requires exactly 1 operand"); } + CastType::Plaintext(PlaintextType::ExternalStruct(locator)) => { + let external_stack = stack.get_external_stack(locator.program_id())?; + let struct_type = external_stack.program().get_struct(locator.resource())?; + struct_checks(struct_type)?; + } CastType::Plaintext(PlaintextType::Struct(struct_name)) => { // Retrieve the struct and ensure it is defined in the program. - let struct_ = stack.program().get_struct(struct_name)?; - - // Ensure the input types length is at least the minimum. - if input_types.len() < N::MIN_STRUCT_ENTRIES { - bail!("Casting to a struct requires at least {} operand(s)", N::MIN_STRUCT_ENTRIES) - } - // Ensure the number of members does not exceed the maximum. - if input_types.len() > N::MAX_STRUCT_ENTRIES { - bail!("Casting to struct '{struct_}' cannot exceed {} members", N::MAX_STRUCT_ENTRIES) - } - - // Ensure that the number of input types is equal to the number of struct members. - ensure!( - input_types.len() == struct_.members().len(), - "Casting to the struct {} requires {} operands, but {} were provided", - struct_.name(), - struct_.members().len(), - input_types.len() - ); - // Ensure the input types match the struct. - for ((_, member_type), input_type) in struct_.members().iter().zip_eq(input_types) { - match input_type { - // Ensure the plaintext type matches the member type. - RegisterType::Plaintext(plaintext_type) => { - ensure!( - member_type == plaintext_type, - "Struct '{struct_name}' member type mismatch: expected '{member_type}', found '{plaintext_type}'" - ) - } - // Ensure the input type cannot be a record (this is unsupported behavior). - RegisterType::Record(record_name) => bail!( - "Struct '{struct_name}' member type mismatch: expected '{member_type}', found record '{record_name}'" - ), - // Ensure the input type cannot be an external record (this is unsupported behavior). - RegisterType::ExternalRecord(locator) => bail!( - "Struct '{struct_name}' member type mismatch: expected '{member_type}', found external record '{locator}'" - ), - // Ensure the input type cannot be a future (this is unsupported behavior). - RegisterType::Future(..) => { - bail!("Struct '{struct_name}' member type mismatch: expected '{member_type}', found future") - } - } - } + let struct_type = stack.program().get_struct(struct_name)?; + struct_checks(struct_type)?; } CastType::Plaintext(PlaintextType::Array(array_type)) => { // Ensure the input types length is at least the minimum. @@ -748,7 +739,7 @@ impl CastOperation { // Ensure the plaintext type matches the member type. RegisterType::Plaintext(plaintext_type) => { ensure!( - plaintext_type == array_type.next_element_type(), + types_equivalent(stack, plaintext_type, stack, array_type.next_element_type())?, "Array element type mismatch: expected '{}', found '{plaintext_type}'", array_type.next_element_type() ) @@ -844,57 +835,102 @@ impl CastOperation { } } -impl CastOperation { - /// A helper method to handle casting to a struct. - fn cast_to_struct( - &self, - stack: &impl StackTrait, - registers: &mut impl RegistersTrait, - struct_name: Identifier, - inputs: Vec>, - ) -> Result<()> { +macro_rules! cast_to_struct_common { + ($N: ident, $struct_name: expr, $inputs: expr, $stack: expr, $plaintext: path, $record: path, $future: path, + $eject_value: expr, $process_member: expr + ) => {{ // Ensure the operands length is at least the minimum. - if inputs.len() < N::MIN_STRUCT_ENTRIES { - bail!("Casting to a struct requires at least {} operand", N::MIN_STRUCT_ENTRIES) + if $inputs.len() < $N::MIN_STRUCT_ENTRIES { + bail!("Casting to a struct requires at least {} operand(s)", N::MIN_STRUCT_ENTRIES) + } + // Ensure the number of members does not exceed the maximum. + if $inputs.len() > $N::MAX_STRUCT_ENTRIES { + bail!("Casting to a struct cannot exceed {} members", N::MAX_STRUCT_ENTRIES) } // Retrieve the struct and ensure it is defined in the program. - let struct_ = stack.program().get_struct(&struct_name)?; + let struct_ = $stack.program().get_struct($struct_name)?; // Ensure that the number of operands is equal to the number of struct members. - if inputs.len() != struct_.members().len() { + if $inputs.len() != struct_.members().len() { bail!( "Casting to the struct {} requires {} operands, but {} were provided", struct_.name(), struct_.members().len(), - inputs.len() + $inputs.len() ) } // Initialize the struct members. let mut members = IndexMap::new(); - for (member, (member_name, member_type)) in inputs.iter().zip_eq(struct_.members()) { + for (member, (member_name, member_type)) in $inputs.iter().zip_eq(struct_.members()) { // Retrieve the plaintext value from the entry. let plaintext = match member { - Value::Plaintext(plaintext) => { - // Ensure the plaintext matches the member type. - stack.matches_plaintext(plaintext, member_type)?; + $plaintext(plaintext) => { + // Ensure the member matches the register type. + $stack.matches_plaintext(&$eject_value(plaintext), member_type)?; // Output the plaintext. plaintext.clone() } // Ensure the struct member is not a record. - Value::Record(..) => bail!("Casting a record into a struct member is illegal"), + $record(..) => { + bail!("Casting a record into a struct member is illegal") + } // Ensure the struct member is not a future. - Value::Future(..) => bail!("Casting a future into a struct member is illegal"), + $future(..) => { + bail!("Casting a future into a struct member is illegal") + } }; // Append the member to the struct members. - members.insert(*member_name, plaintext); + members.insert($process_member(member_name), plaintext); } + members + }}; +} + +impl CastOperation { + fn evaluate_cast_to_struct( + &self, + stack: &impl StackTrait, + struct_name: Identifier, + inputs: Vec>, + ) -> Result> { + let members = cast_to_struct_common!( + N, + &struct_name, + inputs, + stack, + Value::Plaintext, + Value::Record, + Value::Future, + |x: &Plaintext<_>| x.clone(), + |x: &Identifier<_>| *x + ); + // Construct the struct. + Ok(Plaintext::Struct(members, Default::default())) + } + + fn execute_cast_to_struct>( + &self, + stack: &impl StackTrait, + struct_name: Identifier, + inputs: Vec>, + ) -> Result> { + use circuit::{Eject, Inject}; + let members = cast_to_struct_common!( + N, + &struct_name, + inputs, + stack, + circuit::Value::Plaintext, + circuit::Value::Record, + circuit::Value::Future, + |x: &circuit::Plaintext<_>| x.eject_value(), + |x: &Identifier| circuit::Identifier::constant(*x) + ); // Construct the struct. - let struct_ = Plaintext::Struct(members, Default::default()); - // Store the struct. - registers.store(stack, &self.destination, Value::Plaintext(struct_)) + Ok(circuit::Plaintext::Struct(members, Default::default())) } /// A helper method to handle casting to an array. @@ -983,7 +1019,7 @@ impl Parser for CastOperation { CastType::GroupXCoordinate | CastType::GroupYCoordinate | CastType::Plaintext(PlaintextType::Literal(_)) => 1, - CastType::Plaintext(PlaintextType::Struct(_)) => N::MAX_STRUCT_ENTRIES, + CastType::Plaintext(PlaintextType::Struct(_) | PlaintextType::ExternalStruct(_)) => N::MAX_STRUCT_ENTRIES, CastType::Plaintext(PlaintextType::Array(_)) => N::MAX_ARRAY_ELEMENTS, CastType::Record(_) | CastType::ExternalRecord(_) => N::MAX_RECORD_ENTRIES, }; @@ -1031,7 +1067,7 @@ impl Display for CastOperation { CastType::GroupYCoordinate | CastType::GroupXCoordinate | CastType::Plaintext(PlaintextType::Literal(_)) => 1, - CastType::Plaintext(PlaintextType::Struct(_)) => N::MAX_STRUCT_ENTRIES, + CastType::Plaintext(PlaintextType::Struct(_) | PlaintextType::ExternalStruct(_)) => N::MAX_STRUCT_ENTRIES, CastType::Plaintext(PlaintextType::Array(_)) => N::MAX_ARRAY_ELEMENTS, CastType::Record(_) | CastType::ExternalRecord(_) => N::MAX_RECORD_ENTRIES, }; @@ -1080,7 +1116,7 @@ impl FromBytes for CastOperation { CastType::GroupYCoordinate | CastType::GroupXCoordinate | CastType::Plaintext(PlaintextType::Literal(_)) => 1, - CastType::Plaintext(PlaintextType::Struct(_)) => N::MAX_STRUCT_ENTRIES, + CastType::Plaintext(PlaintextType::Struct(_) | PlaintextType::ExternalStruct(_)) => N::MAX_STRUCT_ENTRIES, CastType::Plaintext(PlaintextType::Array(_)) => N::MAX_ARRAY_ELEMENTS, CastType::Record(_) | CastType::ExternalRecord(_) => N::MAX_RECORD_ENTRIES, }; @@ -1101,7 +1137,7 @@ impl ToBytes for CastOperation { CastType::GroupYCoordinate | CastType::GroupXCoordinate | CastType::Plaintext(PlaintextType::Literal(_)) => 1, - CastType::Plaintext(PlaintextType::Struct(_)) => N::MAX_STRUCT_ENTRIES, + CastType::Plaintext(PlaintextType::Struct(_) | PlaintextType::ExternalStruct(_)) => N::MAX_STRUCT_ENTRIES, CastType::Plaintext(PlaintextType::Array(_)) => N::MAX_ARRAY_ELEMENTS, CastType::Record(_) | CastType::ExternalRecord(_) => N::MAX_RECORD_ENTRIES, }; diff --git a/synthesizer/program/src/logic/instruction/operation/deserialize.rs b/synthesizer/program/src/logic/instruction/operation/deserialize.rs index 478c60e193..338c6cab54 100644 --- a/synthesizer/program/src/logic/instruction/operation/deserialize.rs +++ b/synthesizer/program/src/logic/instruction/operation/deserialize.rs @@ -21,6 +21,7 @@ use console::{ Identifier, Literal, LiteralType, + Locator, Plaintext, PlaintextType, Register, @@ -192,27 +193,31 @@ impl DeserializeInstruction { /// /// This allows running `deserialize` without the machinery of stacks and registers. /// This is necessary for the Leo interpreter. -pub fn evaluate_deserialize( +pub fn evaluate_deserialize( variant: DeserializeVariant, bits: &[bool], destination_type: &PlaintextType, - get_struct: &F, + get_struct: &F0, + get_external_struct: &F1, ) -> Result> where - F: Fn(&Identifier) -> Result>, + F0: Fn(&Identifier) -> Result>, + F1: Fn(&Locator) -> Result>, { - evaluate_deserialize_internal(variant as u8, bits, destination_type, get_struct, 0) + evaluate_deserialize_internal(variant as u8, bits, destination_type, get_struct, get_external_struct, 0) } -fn evaluate_deserialize_internal( +fn evaluate_deserialize_internal( variant: u8, bits: &[bool], destination_type: &PlaintextType, - get_struct: &F, + get_struct: &F0, + get_external_struct: &F1, depth: usize, ) -> Result> where - F: Fn(&Identifier) -> Result>, + F0: Fn(&Identifier) -> Result>, + F1: Fn(&Locator) -> Result>, { // Ensure that the depth is within the maximum limit. if depth > N::MAX_DATA_DEPTH { @@ -222,8 +227,8 @@ where // A helper to get the number of bits needed. let get_size_in_bits = |plaintext_type: &PlaintextType| -> Result { match DeserializeVariant::from_u8(variant) { - DeserializeVariant::FromBits => plaintext_type.size_in_bits(&get_struct), - DeserializeVariant::FromBitsRaw => plaintext_type.size_in_bits_raw(&get_struct), + DeserializeVariant::FromBits => plaintext_type.size_in_bits(get_struct, &get_external_struct), + DeserializeVariant::FromBitsRaw => plaintext_type.size_in_bits_raw(get_struct, &get_external_struct), } }; @@ -338,6 +343,7 @@ where next_bits(expected_member_size)?, member_type, get_struct, + get_external_struct, depth + 1, )?; @@ -349,6 +355,7 @@ where // Cache the plaintext bits, and return the struct. Ok(Plaintext::Struct(members, Default::default())) } + PlaintextType::ExternalStruct(_identifier) => todo!(), PlaintextType::Array(array_type) => { // If the variant is `FromBits`, check the variant and metadata. if variant == (DeserializeVariant::FromBits as u8) { @@ -386,6 +393,7 @@ where next_bits(expected_element_size)?, expected_element_type, get_struct, + get_external_struct, depth + 1, )?; elements.push(element); @@ -397,15 +405,17 @@ where } } -fn execute_deserialize_internal, N: Network, F>( +fn execute_deserialize_internal, N: Network, F0, F1>( variant: u8, bits: &[circuit::Boolean], destination_type: &PlaintextType, - get_struct: &F, + get_struct: &F0, + get_external_struct: &F1, depth: usize, ) -> Result> where - F: Fn(&Identifier) -> Result>, + F0: Fn(&Identifier) -> Result>, + F1: Fn(&Locator) -> Result>, { use snarkvm_circuit::{Inject, traits::FromBits}; @@ -417,8 +427,8 @@ where // A helper to get the number of bits needed. let get_size_in_bits = |plaintext_type: &PlaintextType| -> Result { match DeserializeVariant::from_u8(variant) { - DeserializeVariant::FromBits => plaintext_type.size_in_bits(get_struct), - DeserializeVariant::FromBitsRaw => plaintext_type.size_in_bits_raw(get_struct), + DeserializeVariant::FromBits => plaintext_type.size_in_bits(get_struct, get_external_struct), + DeserializeVariant::FromBitsRaw => plaintext_type.size_in_bits_raw(get_struct, get_external_struct), } }; @@ -524,6 +534,7 @@ where next_bits(expected_member_size as usize)?, member_type, get_struct, + get_external_struct, depth + 1, )?; @@ -535,6 +546,7 @@ where // Cache the plaintext bits, and return the struct. Ok(circuit::Plaintext::Struct(members, Default::default())) } + PlaintextType::ExternalStruct(_identifier) => todo!(), PlaintextType::Array(array_type) => { // Get the expected length of the array. let expected_length = **array_type.length(); @@ -567,6 +579,7 @@ where next_bits(expected_element_size as usize)?, expected_element_type, get_struct, + get_external_struct, depth + 1, )?; elements.push(element); @@ -603,10 +616,15 @@ impl DeserializeInstruction { // A helper to get a struct declaration. let get_struct = |identifier: &Identifier| stack.program().get_struct(identifier).cloned(); + // A helper to get an external struct declaration. + let get_external_struct = |locator: &Locator| { + stack.get_external_stack(locator.program_id())?.program().get_struct(locator.resource()).cloned() + }; + // Get the size in bits of the operand. let size_in_bits = match VARIANT { - 0 => self.destination_type.size_in_bits(&get_struct)?, - 1 => self.destination_type.size_in_bits_raw(&get_struct)?, + 0 => self.destination_type.size_in_bits(&get_struct, &get_external_struct)?, + 1 => self.destination_type.size_in_bits_raw(&get_struct, &get_external_struct)?, variant => bail!("Invalid `deserialize` variant '{variant}'"), }; @@ -618,7 +636,14 @@ impl DeserializeInstruction { ); // Deserialize into the desired output. - let output = evaluate_deserialize_internal(VARIANT, &bits, &self.destination_type, &get_struct, 0)?; + let output = evaluate_deserialize_internal( + VARIANT, + &bits, + &self.destination_type, + &get_struct, + &get_external_struct, + 0, + )?; // Store the output. registers.store(stack, &self.destination, Value::Plaintext(output)) @@ -649,10 +674,15 @@ impl DeserializeInstruction { // A helper to get a struct declaration. let get_struct = |identifier: &Identifier| stack.program().get_struct(identifier).cloned(); + // A helper to get an external struct declaration. + let get_external_struct = |locator: &Locator| { + stack.get_external_stack(locator.program_id())?.program().get_struct(locator.resource()).cloned() + }; + // Get the size in bits of the operand. let size_in_bits = match VARIANT { - 0 => self.destination_type.size_in_bits(&get_struct)?, - 1 => self.destination_type.size_in_bits_raw(&get_struct)?, + 0 => self.destination_type.size_in_bits(&get_struct, &get_external_struct)?, + 1 => self.destination_type.size_in_bits_raw(&get_struct, &get_external_struct)?, variant => bail!("Invalid `deserialize` variant '{variant}'"), }; @@ -664,7 +694,8 @@ impl DeserializeInstruction { ); // Deserialize the bits into the desired literal type. - let output = execute_deserialize_internal(VARIANT, &bits, &self.destination_type, &get_struct, 0)?; + let output = + execute_deserialize_internal(VARIANT, &bits, &self.destination_type, &get_struct, &get_external_struct, 0)?; // Store the output. registers.store_circuit(stack, &self.destination, circuit::Value::Plaintext(output)) @@ -699,10 +730,15 @@ impl DeserializeInstruction { // A helper to get a struct declaration. let get_struct = |identifier: &Identifier| stack.program().get_struct(identifier).cloned(); + // A helper to get an external struct declaration. + let get_external_struct = |locator: &Locator| { + stack.get_external_stack(locator.program_id())?.program().get_struct(locator.resource()).cloned() + }; + // Get the size in bits of the operand. let size_in_bits = match VARIANT { - 0 => self.destination_type.size_in_bits(&get_struct)?, - 1 => self.destination_type.size_in_bits_raw(&get_struct)?, + 0 => self.destination_type.size_in_bits(&get_struct, &get_external_struct)?, + 1 => self.destination_type.size_in_bits_raw(&get_struct, &get_external_struct)?, variant => bail!("Invalid `deserialize` variant '{variant}'"), }; diff --git a/synthesizer/program/src/logic/instruction/operation/ecdsa_verify.rs b/synthesizer/program/src/logic/instruction/operation/ecdsa_verify.rs index 5423fcfb19..629da9d814 100644 --- a/synthesizer/program/src/logic/instruction/operation/ecdsa_verify.rs +++ b/synthesizer/program/src/logic/instruction/operation/ecdsa_verify.rs @@ -437,6 +437,11 @@ impl ECDSAVerify { // A helper to get a struct declaration. let get_struct = |identifier: &Identifier| stack.program().get_struct(identifier).cloned(); + // A helper to get an external struct declaration. + let get_external_struct = |locator: &Locator| { + stack.get_external_stack(locator.program_id())?.program().get_struct(locator.resource()).cloned() + }; + // A helper to get a record declaration. let get_record = |identifier: &Identifier| stack.program().get_record(identifier).cloned(); @@ -466,8 +471,20 @@ impl ECDSAVerify { // Get the size in bits of the message. let size_in_bits = match variant.is_raw() { - false => input_types[2].size_in_bits(&get_struct, &get_record, &get_external_record, &get_future)?, - true => input_types[2].size_in_bits_raw(&get_struct, &get_record, &get_external_record, &get_future)?, + false => input_types[2].size_in_bits( + &get_struct, + &get_external_struct, + &get_record, + &get_external_record, + &get_future, + )?, + true => input_types[2].size_in_bits_raw( + &get_struct, + &get_external_struct, + &get_record, + &get_external_record, + &get_future, + )?, }; // Check the number of bits. ensure!( diff --git a/synthesizer/program/src/logic/instruction/operation/hash.rs b/synthesizer/program/src/logic/instruction/operation/hash.rs index 153707e9e8..fd7dc12a16 100644 --- a/synthesizer/program/src/logic/instruction/operation/hash.rs +++ b/synthesizer/program/src/logic/instruction/operation/hash.rs @@ -411,6 +411,7 @@ fn is_valid_destination_type(variant: u8, destination_type: &Plainte PlaintextType::Literal(LiteralType::Boolean) | PlaintextType::Literal(LiteralType::String) | PlaintextType::Struct(..) + | PlaintextType::ExternalStruct(..) | PlaintextType::Array(..) ), 33..=44 => matches!(destination_type, PlaintextType::Array(array_type) if array_type.is_bit_array()), @@ -669,6 +670,11 @@ impl HashInstruction { // A helper to get a struct declaration. let get_struct = |identifier: &Identifier| stack.program().get_struct(identifier).cloned(); + // A helper to get an external struct declaration. + let get_external_struct = |locator: &Locator| { + stack.get_external_stack(locator.program_id())?.program().get_struct(locator.resource()).cloned() + }; + // A helper to get a record declaration. let get_record = |identifier: &Identifier| stack.program().get_record(identifier).cloned(); @@ -698,8 +704,20 @@ impl HashInstruction { // Get the size in bits. let size_in_bits = match variant.is_raw() { - false => input_types[0].size_in_bits(&get_struct, &get_record, &get_external_record, &get_future)?, - true => input_types[0].size_in_bits_raw(&get_struct, &get_record, &get_external_record, &get_future)?, + false => input_types[0].size_in_bits( + &get_struct, + &get_external_struct, + &get_record, + &get_external_record, + &get_future, + )?, + true => input_types[0].size_in_bits_raw( + &get_struct, + &get_external_struct, + &get_record, + &get_external_record, + &get_future, + )?, }; // Check the number of bits. ensure!( diff --git a/synthesizer/program/src/logic/instruction/operation/is.rs b/synthesizer/program/src/logic/instruction/operation/is.rs index cd573fe6a9..f42435b4e2 100644 --- a/synthesizer/program/src/logic/instruction/operation/is.rs +++ b/synthesizer/program/src/logic/instruction/operation/is.rs @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{Opcode, Operand, RegistersCircuit, RegistersTrait, StackTrait}; +use crate::{Opcode, Operand, RegistersCircuit, RegistersTrait, StackTrait, register_types_equivalent}; use console::{ network::prelude::*, program::{Literal, LiteralType, Plaintext, PlaintextType, Register, RegisterType, Value}, @@ -131,7 +131,7 @@ impl IsInstruction { /// Returns the output type from the given program and input types. pub fn output_types( &self, - _stack: &impl StackTrait, + stack: &impl StackTrait, input_types: &[RegisterType], ) -> Result>> { // Ensure the number of input types is correct. @@ -139,7 +139,7 @@ impl IsInstruction { bail!("Instruction '{}' expects 2 inputs, found {} inputs", Self::opcode(), input_types.len()) } // Ensure the operands are of the same type. - if input_types[0] != input_types[1] { + if !register_types_equivalent(stack, &input_types[0], stack, &input_types[1])? { bail!( "Instruction '{}' expects inputs of the same type. Found inputs of type '{}' and '{}'", Self::opcode(), diff --git a/synthesizer/program/src/logic/instruction/operation/literals.rs b/synthesizer/program/src/logic/instruction/operation/literals.rs index cb633c3428..eab21aabae 100644 --- a/synthesizer/program/src/logic/instruction/operation/literals.rs +++ b/synthesizer/program/src/logic/instruction/operation/literals.rs @@ -159,6 +159,7 @@ impl, LiteralType, NUM_OPERANDS>, const N .map(|input_type| match input_type { RegisterType::Plaintext(PlaintextType::Literal(literal_type)) => Ok(*literal_type), RegisterType::Plaintext(PlaintextType::Struct(..)) + | RegisterType::Plaintext(PlaintextType::ExternalStruct(..)) | RegisterType::Plaintext(PlaintextType::Array(..)) | RegisterType::Record(..) | RegisterType::ExternalRecord(..) diff --git a/synthesizer/program/src/logic/instruction/operation/serialize.rs b/synthesizer/program/src/logic/instruction/operation/serialize.rs index 6cfb25baa4..63026adb32 100644 --- a/synthesizer/program/src/logic/instruction/operation/serialize.rs +++ b/synthesizer/program/src/logic/instruction/operation/serialize.rs @@ -16,7 +16,7 @@ use crate::{Opcode, Operand, RegistersCircuit, RegistersTrait, StackTrait}; use console::{ network::prelude::*, - program::{ArrayType, Identifier, LiteralType, Plaintext, PlaintextType, Register, RegisterType, Value}, + program::{ArrayType, Identifier, LiteralType, Locator, Plaintext, PlaintextType, Register, RegisterType, Value}, }; /// Serializes the bits of the input. @@ -299,10 +299,15 @@ impl SerializeInstruction { // A helper to get a struct declaration. let get_struct = |identifier: &Identifier| stack.program().get_struct(identifier).cloned(); + // A helper to get an external struct declaration. + let get_external_struct = |locator: &Locator| { + stack.get_external_stack(locator.program_id())?.program().get_struct(locator.resource()).cloned() + }; + // Get the size in bits of the operand. let size_in_bits = match VARIANT { - 0 => self.operand_type.size_in_bits(&get_struct)?, - 1 => self.operand_type.size_in_bits_raw(&get_struct)?, + 0 => self.operand_type.size_in_bits(&get_struct, &get_external_struct)?, + 1 => self.operand_type.size_in_bits_raw(&get_struct, &get_external_struct)?, variant => bail!("Invalid `serialize` variant '{variant}'"), }; diff --git a/synthesizer/program/src/mapping/mod.rs b/synthesizer/program/src/mapping/mod.rs index c1ee24298f..2e4d27ce23 100644 --- a/synthesizer/program/src/mapping/mod.rs +++ b/synthesizer/program/src/mapping/mod.rs @@ -55,6 +55,10 @@ impl Mapping { &self.value } + pub fn contains_external_struct(&self) -> bool { + self.key.plaintext_type().contains_external_struct() || self.value.plaintext_type().contains_external_struct() + } + /// Returns `true` if the mapping contains a string type. pub fn contains_string_type(&self) -> bool { self.key.plaintext_type().contains_string_type() || self.value.plaintext_type().contains_string_type() diff --git a/synthesizer/program/src/parse.rs b/synthesizer/program/src/parse.rs index ab9a8f565d..a8d07a7ef3 100644 --- a/synthesizer/program/src/parse.rs +++ b/synthesizer/program/src/parse.rs @@ -78,6 +78,18 @@ impl Parser for ProgramCore { return map_res(take(0usize), Err)(string); } }; + + // Add the imports (if any) to the program. + for import in imports { + match program.add_import(import) { + Ok(_) => (), + Err(error) => { + eprintln!("{error}"); + return map_res(take(0usize), Err)(string); + } + } + } + // Construct the program with the parsed components. for component in components { let result = match component { @@ -97,16 +109,6 @@ impl Parser for ProgramCore { } } } - // Lastly, add the imports (if any) to the program. - for import in imports { - match program.add_import(import) { - Ok(_) => (), - Err(error) => { - eprintln!("{error}"); - return map_res(take(0usize), Err)(string); - } - } - } Ok((string, program)) } diff --git a/synthesizer/program/src/traits/stack_and_registers.rs b/synthesizer/program/src/traits/stack_and_registers.rs index cd51e35f33..3f7b78d51a 100644 --- a/synthesizer/program/src/traits/stack_and_registers.rs +++ b/synthesizer/program/src/traits/stack_and_registers.rs @@ -31,6 +31,7 @@ use console::{ Record, Register, RegisterType, + StructType, Value, ValueType, }, @@ -150,6 +151,109 @@ pub trait StackTrait { ) -> Result>>; } +/// Are the two types either the same, or both structurally equivalent `PlaintextType`s? +pub fn register_types_equivalent( + stack0: &impl StackTrait, + type0: &RegisterType, + stack1: &impl StackTrait, + type1: &RegisterType, +) -> Result { + use RegisterType::*; + if let (Plaintext(plaintext0), Plaintext(plaintext1)) = (type0, type1) { + types_equivalent(stack0, plaintext0, stack1, plaintext1) + } else { + Ok(type0 == type1) + } +} + +/// Determines whether two `PlaintextType` values are equivalent. +/// +/// Equivalence of literals means they're the same type. +/// +/// Equivalence of structs means they have the same local names (regardless of whether +/// they're local or external), and their members have the same names and equivalent +/// types in the same order, recursively. +/// +/// Equivalence of arrays means they have the same length and their element types are +/// equivalent. +/// +/// This definition of equivalence was chosen to balance these concerns: +/// +/// 1. All programs from before the existence of external structs will continue to work; +/// thus it's necessary for a struct created from another program to be considered equivalent +/// to a local one with the same name and structure, as in practice that was the behavior. +/// 2. We don't want to allow a fork. Thus we do need to check names, not just structural +/// equivalence - otherwise we could get a program deployable to a node which is using +/// this check, but not deployable to a node running an earlier SnarkVM. +/// +/// The stacks are passed because struct types need to access their stack to get their +/// structure. +pub fn types_equivalent( + stack0: &impl StackTrait, + type0: &PlaintextType, + stack1: &impl StackTrait, + type1: &PlaintextType, +) -> Result { + use PlaintextType::*; + + let struct_compare = |stack0, st0: &StructType, stack1, st1: &StructType| -> Result { + if st0.members().len() != st1.members().len() { + return Ok(false); + } + + for ((name0, type0), (name1, type1)) in st0.members().iter().zip(st1.members()) { + if name0 != name1 || !types_equivalent(stack0, type0, stack1, type1)? { + return Ok(false); + } + } + + Ok(true) + }; + + match (type0, type1) { + (Array(array0), Array(array1)) => Ok(array0.length() == array1.length() + && types_equivalent(stack0, array0.next_element_type(), stack1, array1.next_element_type())?), + (Literal(lit0), Literal(lit1)) => Ok(lit0 == lit1), + (Struct(id0), Struct(id1)) => { + if id0 != id1 { + return Ok(false); + } + let struct_type0 = stack0.program().get_struct(id0)?; + let struct_type1 = stack1.program().get_struct(id1)?; + struct_compare(stack0, struct_type0, stack1, struct_type1) + } + (ExternalStruct(loc0), ExternalStruct(loc1)) => { + if loc0.resource() != loc1.resource() { + return Ok(false); + } + let external_stack0 = stack0.get_external_stack(loc0.program_id())?; + let struct_type0 = external_stack0.program().get_struct(loc0.resource())?; + let external_stack1 = stack1.get_external_stack(loc1.program_id())?; + let struct_type1 = external_stack1.program().get_struct(loc1.resource())?; + struct_compare(&*external_stack0, struct_type0, &*external_stack1, struct_type1) + } + (ExternalStruct(loc), Struct(id)) => { + if loc.resource() != id { + return Ok(false); + } + let external_stack = stack0.get_external_stack(loc.program_id())?; + let struct_type0 = external_stack.program().get_struct(loc.resource())?; + let struct_type1 = stack1.program().get_struct(id)?; + struct_compare(&*external_stack, struct_type0, stack1, struct_type1) + } + (Struct(id), ExternalStruct(loc)) => { + if id != loc.resource() { + return Ok(false); + } + let struct_type0 = stack0.program().get_struct(id)?; + let external_stack = stack1.get_external_stack(loc.program_id())?; + let struct_type1 = external_stack.program().get_struct(loc.resource())?; + struct_compare(stack0, struct_type0, &*external_stack, struct_type1) + } + _ => Ok(false), + } +} + pub trait FinalizeRegistersState: RegistersTrait { /// Returns the global state for the finalize scope. fn state(&self) -> &FinalizeGlobalState; diff --git a/synthesizer/program/tests/instruction/deserialize.rs b/synthesizer/program/tests/instruction/deserialize.rs index cd65d78f36..125b0b560c 100644 --- a/synthesizer/program/tests/instruction/deserialize.rs +++ b/synthesizer/program/tests/instruction/deserialize.rs @@ -19,7 +19,7 @@ use circuit::{AleoV0, Eject}; use console::{ network::MainnetV0, prelude::*, - program::{ArrayType, Identifier, LiteralType, Plaintext, PlaintextType, Register, U32, Value}, + program::{ArrayType, Identifier, LiteralType, Locator, Plaintext, PlaintextType, Register, U32, Value}, }; use snarkvm_synthesizer_process::{Process, Stack}; use snarkvm_synthesizer_program::{ @@ -97,11 +97,12 @@ fn check_deserialize( // Struct definitions are not supported. let fail_get_struct = |_: &Identifier| bail!("structs are not supported"); + let fail_get_external_struct = |_: &Locator| bail!("structs are not supported"); // Get the size in bits. let size_in_bits = match VARIANT { - 0 => type_.size_in_bits(&fail_get_struct).unwrap(), - 1 => type_.size_in_bits_raw(&fail_get_struct).unwrap(), + 0 => type_.size_in_bits(&fail_get_struct, &fail_get_external_struct).unwrap(), + 1 => type_.size_in_bits_raw(&fail_get_struct, &fail_get_external_struct).unwrap(), _ => panic!("Invalid 'deserialize' variant"), }; let size_in_bits = u32::try_from(size_in_bits).unwrap(); diff --git a/synthesizer/program/tests/instruction/serialize.rs b/synthesizer/program/tests/instruction/serialize.rs index a3ecd689b1..f8b6b76b5b 100644 --- a/synthesizer/program/tests/instruction/serialize.rs +++ b/synthesizer/program/tests/instruction/serialize.rs @@ -19,7 +19,7 @@ use circuit::{AleoV0, Eject}; use console::{ network::MainnetV0, prelude::*, - program::{ArrayType, Identifier, LiteralType, PlaintextType, Register, U32, Value}, + program::{ArrayType, Identifier, LiteralType, Locator, PlaintextType, Register, U32, Value}, }; use snarkvm_synthesizer_process::{Process, Stack}; use snarkvm_synthesizer_program::{ @@ -97,11 +97,12 @@ fn check_serialize( // Struct definitions are not supported. let fail_get_struct = |_: &Identifier| bail!("structs are not supported"); + let fail_get_external_struct = |_: &Locator| bail!("structs are not supported"); // Get the size in bits. let size_in_bits = match VARIANT { - 0 => type_.size_in_bits(&fail_get_struct).unwrap(), - 1 => type_.size_in_bits_raw(&fail_get_struct).unwrap(), + 0 => type_.size_in_bits(&fail_get_struct, &fail_get_external_struct).unwrap(), + 1 => type_.size_in_bits_raw(&fail_get_struct, &fail_get_external_struct).unwrap(), _ => panic!("Invalid 'serialize' variant"), }; let size_in_bits = u32::try_from(size_in_bits).unwrap(); diff --git a/synthesizer/src/vm/tests/mod.rs b/synthesizer/src/vm/tests/mod.rs index 95809eae29..361f46e0c8 100644 --- a/synthesizer/src/vm/tests/mod.rs +++ b/synthesizer/src/vm/tests/mod.rs @@ -25,5 +25,8 @@ mod test_v10; #[cfg(feature = "test")] mod test_v11; +#[cfg(feature = "test")] +mod test_v12; + #[cfg(feature = "test")] use super::*; diff --git a/synthesizer/src/vm/tests/test_v12.rs b/synthesizer/src/vm/tests/test_v12.rs new file mode 100644 index 0000000000..c7b2577d85 --- /dev/null +++ b/synthesizer/src/vm/tests/test_v12.rs @@ -0,0 +1,105 @@ +// Copyright (c) 2019-2025 Provable Inc. +// This file is part of the snarkVM library. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::*; + +use crate::vm::test_helpers::*; + +use snarkvm_synthesizer_program::Program; + +use console::network::ConsensusVersion; +use snarkvm_utilities::TestRng; + +// This test verifies that a program with external structs cannot be deployed on +// consensus version 9. +#[test] +fn test_deploy_external_structs_v10() { + // Use V10 rather than V11 to make sure we still won't be on V12 + // when deploying the second program. + let block = deploy_programs(ConsensusVersion::V10); + + assert_eq!(block.transactions().num_accepted(), 0); + assert_eq!(block.transactions().num_rejected(), 0); + assert_eq!(block.aborted_transaction_ids().len(), 1); +} + +// This test verifies that a program with external structs can be deployed on +// consensus version 12. +#[test] +fn test_deploy_external_structs_v12() { + let block = deploy_programs(ConsensusVersion::V12); + + assert_eq!(block.transactions().num_accepted(), 1); + assert_eq!(block.transactions().num_rejected(), 0); + assert_eq!(block.aborted_transaction_ids().len(), 0); +} + +fn deploy_programs(consensus_version: ConsensusVersion) -> Block { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng); + + // Initialize the VM at the correct height. + let v10_height = CurrentNetwork::CONSENSUS_HEIGHT(consensus_version).unwrap(); + let vm = crate::vm::test_helpers::sample_vm_at_height(v10_height, rng); + + // Define the first program with a record. + let program_one = Program::from_str( + r" +program test_one.aleo; + +constructor: + assert.eq true true; + +struct S: + x as field; + +function make_s: + cast 0field into r0 as S; + output r0 as S.public; +", + ) + .unwrap(); + + // Define the second program which refers to the external struct type. + let program_two = Program::from_str( + r" +import test_one.aleo; + +program test_two.aleo; + +constructor: + assert.eq true true; + +function second: + call test_one.aleo/make_s into r0; + output r0 as test_one.aleo/S.public; +", + ) + .unwrap(); + + // Deploy the first program. + let deployment_one = vm.deploy(&caller_private_key, &program_one, None, 0, None, rng).unwrap(); + let block = sample_next_block(&vm, &caller_private_key, &[deployment_one], rng).unwrap(); + assert_eq!(block.transactions().num_accepted(), 1); + assert_eq!(block.transactions().num_rejected(), 0); + assert_eq!(block.aborted_transaction_ids().len(), 0); + vm.add_next_block(&block).unwrap(); + + // Deploy the second program. + let deployment_two = vm.deploy(&caller_private_key, &program_two, None, 0, None, rng).unwrap(); + sample_next_block(&vm, &caller_private_key, &[deployment_two], rng).unwrap() +} diff --git a/synthesizer/src/vm/verify.rs b/synthesizer/src/vm/verify.rs index 37e9fec4ce..f41a5cfbac 100644 --- a/synthesizer/src/vm/verify.rs +++ b/synthesizer/src/vm/verify.rs @@ -188,6 +188,7 @@ impl> VM { ); // Verify the signature corresponds to the transaction ID. ensure!(owner.verify(*deployment_id), "Invalid owner signature for deployment transaction '{id}'"); + // If the `CONSENSUS_VERSION` is less than `V8`, ensure that // - the deployment edition is zero. // If the `CONSENSUS_VERSION` is less than `V9` ensure that @@ -245,6 +246,21 @@ impl> VM { ); } + // If the `CONSENSUS_VERSION` is less than `V12`, then verify that: + // - the program does not use the external struct syntax `some_program.aleo/StructT` + // If the `CONSENSUS_VERSION` is greater than or equal to `V12`, then verify that: + // - the program's mappings do not use non-existent structs. + if consensus_version < ConsensusVersion::V12 { + ensure!( + !deployment.program().contains_external_struct(), + "Invalid deployment transaction '{id}' - external structs may only be used beginning with Consensus version 10" + ); + } + + if consensus_version >= ConsensusVersion::V12 { + self.process.read().mapping_types_exist(deployment.program())?; + } + // If the program owner exists in the deployment, then verify that it matches the owner in the transaction. if let Some(given_owner) = deployment.program_owner() { // Ensure the program owner matches the owner in the transaction. diff --git a/synthesizer/tests/expectations/parser/program/external_struct.out b/synthesizer/tests/expectations/parser/program/external_struct.out new file mode 100644 index 0000000000..b7dc87efed --- /dev/null +++ b/synthesizer/tests/expectations/parser/program/external_struct.out @@ -0,0 +1 @@ +Program was successfully parsed. \ No newline at end of file diff --git a/synthesizer/tests/expectations/process/execute/external_struct.out b/synthesizer/tests/expectations/process/execute/external_struct.out new file mode 100644 index 0000000000..541f7977a9 --- /dev/null +++ b/synthesizer/tests/expectations/process/execute/external_struct.out @@ -0,0 +1,42 @@ +errors: [] +outputs: +- - |- + [ + { + x: 4field + }, + { + x: 4field + } + ] +- - |- + { + x: 12field + } +- - |- + { + x: 12field + } +- - |- + { + x: 3field, + s: { + x: 3field + } + } +- - 1field +- - 2field +- - |- + { + x: 101field + } +- - |- + { + s: { + x: 100field + } + } +- - |- + { + x: 100field + } diff --git a/synthesizer/tests/expectations/process/execute/external_struct2.out b/synthesizer/tests/expectations/process/execute/external_struct2.out new file mode 100644 index 0000000000..06d59c487f --- /dev/null +++ b/synthesizer/tests/expectations/process/execute/external_struct2.out @@ -0,0 +1,3 @@ +errors: [] +outputs: +- [] diff --git a/synthesizer/tests/expectations/process/execute/external_struct_fail.out b/synthesizer/tests/expectations/process/execute/external_struct_fail.out new file mode 100644 index 0000000000..07a276f1ea --- /dev/null +++ b/synthesizer/tests/expectations/process/execute/external_struct_fail.out @@ -0,0 +1,3 @@ +errors: +- Struct 'S2' in 'external_struct_fail_parent.aleo' is not defined. +outputs: [] diff --git a/synthesizer/tests/expectations/process/execute/external_struct_fail_name.out b/synthesizer/tests/expectations/process/execute/external_struct_fail_name.out new file mode 100644 index 0000000000..c04e858cf1 --- /dev/null +++ b/synthesizer/tests/expectations/process/execute/external_struct_fail_name.out @@ -0,0 +1,3 @@ +errors: +- 'Output ''r0'' does not match the expected output operand type: expected ''external_struct_fail_parent.aleo/S'', found ''T''' +outputs: [] diff --git a/synthesizer/tests/expectations/vm/execute_and_finalize/external_struct.out b/synthesizer/tests/expectations/vm/execute_and_finalize/external_struct.out new file mode 100644 index 0000000000..8443836774 --- /dev/null +++ b/synthesizer/tests/expectations/vm/execute_and_finalize/external_struct.out @@ -0,0 +1,16 @@ +errors: [] +outputs: +- verified: true + execute: + external_struct_test.aleo/main: + outputs: [] + speculate: the execution was accepted + add_next_block: succeeded. +additional: +- child_outputs: + external_struct.aleo/make_struct: + outputs: + - '{"type":"private","id":"950054391566066141831813426021543606350122839180822212323121576500426543341field","value":"ciphertext1qyq9gpt3enh23ry02wmvj4zyk3w3jkk6gga04054jxpq6ctqz8hz6qgh2g64u"}' + credits.aleo/fee_public: + outputs: + - '{"type":"future","id":"3048490372427538183609819194439442577604813977270947453894798278641990313104field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx,\n 2454u64\n ]\n}"}' diff --git a/synthesizer/tests/expectations/vm/execute_and_finalize/external_struct_in_mapping.out b/synthesizer/tests/expectations/vm/execute_and_finalize/external_struct_in_mapping.out new file mode 100644 index 0000000000..c6dfd70a5e --- /dev/null +++ b/synthesizer/tests/expectations/vm/execute_and_finalize/external_struct_in_mapping.out @@ -0,0 +1,67 @@ +errors: [] +outputs: +- verified: true + execute: + parent.aleo/store_t: + outputs: + - '{"type":"future","id":"6801504684493219302823789405686224386785657517434152274736013398978329144845field","value":"{\n program_id: parent.aleo,\n function_name: store_t,\n arguments: [\n {\n x: 12field\n}\n ]\n}"}' + speculate: the execution was accepted + add_next_block: succeeded. +- verified: true + execute: + parent.aleo/store_s: + outputs: + - '{"type":"future","id":"391874225078724773571924710844101851974151637265974521076487544577584656434field","value":"{\n program_id: parent.aleo,\n function_name: store_s,\n arguments: [\n {\n x: 12field\n}\n ]\n}"}' + speculate: the execution was accepted + add_next_block: succeeded. +- verified: true + execute: + parent.aleo/store_s_as_t: + outputs: + - '{"type":"future","id":"4059613284155828593649148683363481039153998827084982120202239631116761077539field","value":"{\n program_id: parent.aleo,\n function_name: store_s_as_t,\n arguments: [\n {\n x: 12field\n}\n ]\n}"}' + speculate: the execution was accepted + add_next_block: succeeded. +- verified: true + execute: + parent.aleo/baz_keys: + outputs: + - '{"type":"future","id":"4178532581508837032088555072114372004008624381630945776472369807190726177644field","value":"{\n program_id: parent.aleo,\n function_name: baz_keys,\n arguments: [\n {\n x: 12field\n}\n ]\n}"}' + speculate: the execution was accepted + add_next_block: succeeded. +- verified: true + execute: + parent.aleo/compare: + outputs: + - '{"type":"future","id":"1414123528844326309044566546573721844182310369101304450030781589321056115963field","value":"{\n program_id: parent.aleo,\n function_name: compare,\n arguments: []\n}"}' + speculate: the execution was accepted + add_next_block: succeeded. +additional: +- child_outputs: + credits.aleo/fee_public: + outputs: + - '{"type":"future","id":"1258946943149191346174440740558254412317036443743799871691007628548951010151field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx,\n 1906u64\n ]\n}"}' +- child_outputs: + child.aleo/make_s: + outputs: + - '{"type":"public","id":"6985903130429112305577914343133100080684294095291138773079739129624829511258field","value":"{\n x: 12field\n}"}' + credits.aleo/fee_public: + outputs: + - '{"type":"future","id":"246687940996289351695445329752398582117713847287771695321740966976889183612field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx,\n 2618u64\n ]\n}"}' +- child_outputs: + child.aleo/make_s: + outputs: + - '{"type":"public","id":"7574377799660344748771224282773834068174701455625760560698920840088755673712field","value":"{\n x: 12field\n}"}' + credits.aleo/fee_public: + outputs: + - '{"type":"future","id":"5440840329189881100372243535086981229911786836863090756813358008537885548138field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx,\n 2688u64\n ]\n}"}' +- child_outputs: + child.aleo/make_s: + outputs: + - '{"type":"public","id":"5642997165471718018783739778741660536733800233179096103171448587842404180286field","value":"{\n x: 12field\n}"}' + credits.aleo/fee_public: + outputs: + - '{"type":"future","id":"7991640413705189295055666104444253675861288502787109993559864263765680782635field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx,\n 2620u64\n ]\n}"}' +- child_outputs: + credits.aleo/fee_public: + outputs: + - '{"type":"future","id":"4087075296851859690375077386496570802162611548430680128084524488091740020645field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx,\n 1363u64\n ]\n}"}' diff --git a/synthesizer/tests/expectations/vm/execute_and_finalize/mapping_with_nonexistent_structs.out b/synthesizer/tests/expectations/vm/execute_and_finalize/mapping_with_nonexistent_structs.out new file mode 100644 index 0000000000..708d0467a4 --- /dev/null +++ b/synthesizer/tests/expectations/vm/execute_and_finalize/mapping_with_nonexistent_structs.out @@ -0,0 +1,3 @@ +errors: +- 'Failed to run `VM::deploy for program child.aleo: Struct ''S'' in ''child.aleo'' is not defined.' +outputs: [] diff --git a/synthesizer/tests/expectations/vm/execute_and_finalize/mapping_with_nonexistent_structs_child.out b/synthesizer/tests/expectations/vm/execute_and_finalize/mapping_with_nonexistent_structs_child.out new file mode 100644 index 0000000000..dca56e01f1 --- /dev/null +++ b/synthesizer/tests/expectations/vm/execute_and_finalize/mapping_with_nonexistent_structs_child.out @@ -0,0 +1,3 @@ +errors: +- 'Failed to run `VM::deploy for program parent.aleo: Struct ''S'' in ''child.aleo'' is not defined.' +outputs: [] diff --git a/synthesizer/tests/tests/parser/program/external_struct.aleo b/synthesizer/tests/tests/parser/program/external_struct.aleo new file mode 100644 index 0000000000..7d42ee5b23 --- /dev/null +++ b/synthesizer/tests/tests/parser/program/external_struct.aleo @@ -0,0 +1,9 @@ +program child.aleo; + +function child: + call parent.aleo/parent into r0; + output r0 as parent.aleo/S.public; + +function child2: + input r0 as parent.aleo/S.public; + output r0 as parent.aleo/S.public; diff --git a/synthesizer/tests/tests/process/execute/external_struct.aleo b/synthesizer/tests/tests/process/execute/external_struct.aleo new file mode 100644 index 0000000000..5ac3d9d8c1 --- /dev/null +++ b/synthesizer/tests/tests/process/execute/external_struct.aleo @@ -0,0 +1,126 @@ +/* +randomness: 45791624 +cases: + - program: child.aleo + function: call_make_array + inputs: [] + - program: child.aleo + function: make_s + inputs: [] + - program: child.aleo + function: make_t + inputs: [] + - program: child.aleo + function: call_make_outer + inputs: [] + - program: child.aleo + function: pass_s + inputs: [] + - program: child.aleo + function: pass_t + inputs: [] + - program: child.aleo + function: cast_s + inputs: [] + - program: child2.aleo + function: call_make_outer2 + inputs: [] + - program: child2.aleo + function: call_make_outer2_access + inputs: [] +*/ + +program external_struct_parent.aleo; + +struct S: + x as field; + +struct Outer: + x as field; + s as S; + +function make_s: + cast 12field into r0 as S; + output r0 as S.public; + +function make_outer: + input r0 as field.public; + cast r0 into r1 as S; + cast r0 r1 into r2 as Outer; + output r2 as Outer.public; + +function make_array: + input r0 as field.public; + cast r0 into r1 as S; + cast r1 r1 into r2 as [S; 2u32]; + output r2 as [S; 2u32].public; + +function accept_s: + input r0 as S.public; + output r0.x as field.public; + +///////////////////////////////////////////////// + +import external_struct_parent.aleo; + +program child.aleo; + +struct S: + x as field; + +struct Outer: + x as field; + s as S; + +struct Outer2: + s as external_struct_parent.aleo/S; + +function make_s: + call external_struct_parent.aleo/make_s into r0; + output r0 as external_struct_parent.aleo/S.public; + +function make_t: + call external_struct_parent.aleo/make_s into r0; + output r0 as S.public; + +function call_make_outer: + call external_struct_parent.aleo/make_outer 3field into r0; + output r0 as Outer.public; + +function call_make_array: + call external_struct_parent.aleo/make_array 4field into r0; + output r0 as [S; 2u32].public; + +function pass_s: + cast 1field into r0 as external_struct_parent.aleo/S; + call external_struct_parent.aleo/accept_s r0 into r1; + output r1 as field.public; + +function pass_t: + cast 2field into r0 as S; + call external_struct_parent.aleo/accept_s r0 into r1; + output r1 as field.public; + +function make_outer2: + cast 100field into r0 as external_struct_parent.aleo/S; + cast r0 into r1 as Outer2; + output r1 as Outer2.public; + +function cast_s: + cast 101field into r0 as external_struct_parent.aleo/S; + output r0 as external_struct_parent.aleo/S.public; + +///////////////////////////////////////////////// + +import child.aleo; +import external_struct_parent.aleo; + +program child2.aleo; + +function call_make_outer2: + call child.aleo/make_outer2 into r0; + output r0 as child.aleo/Outer2.public; + +function call_make_outer2_access: + call child.aleo/make_outer2 into r0; + output r0.s as external_struct_parent.aleo/S.public; diff --git a/synthesizer/tests/tests/process/execute/external_struct2.aleo b/synthesizer/tests/tests/process/execute/external_struct2.aleo new file mode 100644 index 0000000000..6f6781e4b6 --- /dev/null +++ b/synthesizer/tests/tests/process/execute/external_struct2.aleo @@ -0,0 +1,46 @@ +/* +randomness: 45791624 +cases: + - program: external_struct2_child.aleo + function: main + inputs: [1u8] +*/ + +program external_struct2_parent.aleo; + +struct S: + data as u8; + +function make_struct: + input r0 as u8.private; + cast r0 into r1 as S; + output r1 as S.private; + +///////////////////////////////////////////////// + + +import external_struct2_parent.aleo; + +program external_struct2_child.aleo; + +struct S: + data as u8; + +closure check_structs: + input r0 as external_struct2_parent.aleo/S; + input r1 as S; + is.eq r0 r1 into r2; + is.neq r0 r1 into r3; + assert.eq r0 r1; + assert.eq r2 true; + assert.eq r3 false; + +function main: + input r0 as u8.private; + call external_struct2_parent.aleo/make_struct r0 into r1; + call external_struct2_parent.aleo/make_struct r0 into r2; + cast r0 into r3 as S; + assert.eq r1 r2; + assert.eq r1 r3; + call check_structs r1 r2; + call check_structs r1 r3; diff --git a/synthesizer/tests/tests/process/execute/external_struct_fail.aleo b/synthesizer/tests/tests/process/execute/external_struct_fail.aleo new file mode 100644 index 0000000000..f0d574188a --- /dev/null +++ b/synthesizer/tests/tests/process/execute/external_struct_fail.aleo @@ -0,0 +1,27 @@ +/* +randomness: 45791624 +cases: [] +*/ + +program external_struct_fail_parent.aleo; + +struct S: + x as field; + +function make_s: + cast 12field into r0 as S; + output r0 as S.public; + +///////////////////////////////////////////////// + +import external_struct_fail_parent.aleo; + +program external_struct_fail_child.aleo; + +struct T: + x as field; + +function make_s: + // This should fail as the struct doesn't exist. + cast 2field into r0 as external_struct_fail_parent.aleo/S2; + output r0 as external_struct_fail_parent.aleo/S2.public; diff --git a/synthesizer/tests/tests/process/execute/external_struct_fail_name.aleo b/synthesizer/tests/tests/process/execute/external_struct_fail_name.aleo new file mode 100644 index 0000000000..716de8b55d --- /dev/null +++ b/synthesizer/tests/tests/process/execute/external_struct_fail_name.aleo @@ -0,0 +1,27 @@ +/* +randomness: 45791624 +cases: [] +*/ + +program external_struct_fail_parent.aleo; + +struct S: + x as field; + +function make_s: + cast 12field into r0 as S; + output r0 as S.public; + +///////////////////////////////////////////////// + +import external_struct_fail_parent.aleo; + +program external_struct_fail_child.aleo; + +struct T: + x as field; + +function make_s: + call external_struct_fail_parent.aleo/make_s into r0; + // This should fail as the struct names don't match. + output r0 as T.public; diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/external_struct.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/external_struct.aleo new file mode 100644 index 0000000000..3bd6b79ad9 --- /dev/null +++ b/synthesizer/tests/tests/vm/execute_and_finalize/external_struct.aleo @@ -0,0 +1,48 @@ +/* +start_height: 19 +randomness: 45791624 +cases: + - program: external_struct_test.aleo + function: main + inputs: ["0u8"] +*/ + +program external_struct.aleo; + +struct S: + data as u8; + +function make_struct: + input r0 as u8.private; + cast r0 into r1 as S; + output r1 as S.private; + +constructor: + assert.eq edition 0u16; + + +///////////////////////////////////////////////// + +import external_struct.aleo; +program external_struct_test.aleo; + +struct S: + data as u8; + +closure check_structs: + input r0 as S; + input r1 as S; + assert.eq r0 r1; + +function main: + input r0 as u8.private; + call external_struct.aleo/make_struct r0 into r1; + call external_struct.aleo/make_struct r0 into r2; + cast r0 into r3 as S; + assert.eq r1 r2; + assert.eq r1 r3; + call check_structs r1 r2; + call check_structs r1 r3; + +constructor: + assert.eq edition 0u16; diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/external_struct_in_mapping.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/external_struct_in_mapping.aleo new file mode 100644 index 0000000000..686c42ce30 --- /dev/null +++ b/synthesizer/tests/tests/vm/execute_and_finalize/external_struct_in_mapping.aleo @@ -0,0 +1,104 @@ +/* +start_height: 19 +randomness: 45791624 +cases: + - program: parent.aleo + function: store_t + inputs: [] + - program: parent.aleo + function: store_s + inputs: [] + - program: parent.aleo + function: store_s_as_t + inputs: [] + - program: parent.aleo + function: baz_keys + inputs: [] + - program: parent.aleo + function: compare + inputs: [] +*/ + +program child.aleo; + +struct S: + x as field; + +function make_s: + cast 12field into r0 as S; + output r0 as S.public; + +constructor: + assert.eq edition 0u16; + +///////////////////////////////////////////////// + +import child.aleo; + +program parent.aleo; + +struct S: + x as field; + +mapping foo: + key as field.public; + value as S.public; + +mapping bar: + key as field.public; + value as child.aleo/S.public; + +mapping baz: + key as S.public; + value as field.public; + +closure make_t: + input r0 as field; + cast r0 into r1 as S; + output r1 as S; + +function store_t: + call make_t 12field into r0; + async store_t r0 into r1; + output r1 as parent.aleo/store_t.future; +finalize store_t: + input r0 as S.public; + set r0 into foo[0field]; + +function store_s: + call child.aleo/make_s into r0; + async store_s r0 into r1; + output r1 as parent.aleo/store_s.future; +finalize store_s: + input r0 as child.aleo/S.public; + set r0 into bar[1field]; + +function store_s_as_t: + call child.aleo/make_s into r0; + async store_s_as_t r0 into r1; + output r1 as parent.aleo/store_s_as_t.future; +finalize store_s_as_t: + input r0 as child.aleo/S.public; + cast r0.x into r1 as S; + set r1 into foo[2field]; + +function baz_keys: + call child.aleo/make_s into r0; + async baz_keys r0 into r1; + output r1 as parent.aleo/baz_keys.future; +finalize baz_keys: + input r0 as child.aleo/S.public; + set 1field into baz[r0]; + +function compare: + async compare into r0; + output r0 as parent.aleo/compare.future; +finalize compare: + get foo[0field] into r0; + get bar[1field] into r1; + assert.eq r0.x r1.x; + + +constructor: + assert.eq edition 0u16; + diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/mapping_with_nonexistent_structs.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/mapping_with_nonexistent_structs.aleo new file mode 100644 index 0000000000..83aecf0f6f --- /dev/null +++ b/synthesizer/tests/tests/vm/execute_and_finalize/mapping_with_nonexistent_structs.aleo @@ -0,0 +1,16 @@ +/* +start_height: 19 +randomness: 45791624 +cases: [] +*/ + +program child.aleo; + +mapping foo: + key as field.public; + value as S.public; + +function dummy: + +constructor: + assert.eq edition 0u16; diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/mapping_with_nonexistent_structs_child.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/mapping_with_nonexistent_structs_child.aleo new file mode 100644 index 0000000000..651dd3d193 --- /dev/null +++ b/synthesizer/tests/tests/vm/execute_and_finalize/mapping_with_nonexistent_structs_child.aleo @@ -0,0 +1,28 @@ +/* +start_height: 19 +randomness: 45791624 +cases: [] +*/ + +program child.aleo; + +function dummy: + +constructor: + assert.eq edition 0u16; + +///////////////////////////////////////////////// + +import child.aleo; + +program parent.aleo; + +mapping foo: + key as field.public; + value as child.aleo/S.public; + +function dummy: + +constructor: + assert.eq edition 0u16; +