Skip to content
Open
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
558625f
Allow external structs.
mikebenfield Jun 25, 2025
46bf2c1
Merge branch 'staging' into struct-namespace
mikebenfield Jul 25, 2025
e3601da
Disallow deployments using external struct before ConsensusVersion::V10.
mikebenfield Jul 25, 2025
839f17d
Added more varied uses of external structs in parsing test.
mikebenfield Jul 29, 2025
4112865
Merge branch 'staging' into struct-namespace
mikebenfield Jul 29, 2025
0085736
Fix external_struct_fail test
mikebenfield Jul 30, 2025
d7e0691
Merge branch 'staging' into struct-namespace
mikebenfield Jul 30, 2025
04b2a49
Fix tests
mikebenfield Jul 30, 2025
593ea83
Merge branch 'staging' into struct-namespace
mikebenfield Aug 1, 2025
2c1ca39
Gate external structs by < V10 rather than <= V9
mikebenfield Aug 1, 2025
ad6c262
Correct doc comments to say external structs
mikebenfield Aug 1, 2025
bb0b0f9
Respond to review comments.
mikebenfield Aug 11, 2025
3d9c3a3
Fix some comments in response to review
mikebenfield Aug 14, 2025
9c604cc
next_element_type in equal_or_structs
mikebenfield Aug 14, 2025
c854945
Add some tests
d0cd Aug 15, 2025
d4f0842
Add regression
d0cd Aug 15, 2025
3b5fb89
Check types for structural equivalence.
mikebenfield Aug 18, 2025
832e38a
Merge branch 'staging' into struct-namespace
mikebenfield Aug 20, 2025
0f4bb1b
Address review comments.
mikebenfield Aug 24, 2025
71aef7f
Test accessing a field of an external struct
mikebenfield Aug 24, 2025
e5f7e3b
clippy
mikebenfield Aug 24, 2025
377e066
Disallow nonexistent structs in mappings
mikebenfield Aug 28, 2025
95364fc
Merge branch 'staging' into struct-namespace
mikebenfield Aug 28, 2025
ee0e0ee
Merge branch 'staging' into struct-namespace
mikebenfield Sep 2, 2025
c015c7d
Move type checks for mapping types to check_transaction
mikebenfield Sep 16, 2025
505aecc
Merge branch 'staging' into struct-namespace
mikebenfield Sep 16, 2025
7e02f20
Fix mapping type check
mikebenfield Sep 16, 2025
2985f4e
comments
mikebenfield Sep 19, 2025
55038dc
test_v11.rs
mikebenfield Sep 20, 2025
1204947
Review comments.
mikebenfield Sep 25, 2025
4348c3f
Merge branch 'staging' into struct-namespace
mikebenfield Sep 25, 2025
b5826a2
Fix external_struct_in_mapping test
mikebenfield Sep 30, 2025
96ee3a0
Merge branch 'staging' into struct-namespace
mikebenfield Sep 30, 2025
fd0779c
Merge branch 'staging' into struct-namespace
Nov 3, 2025
7a16a80
Fix a few things
Nov 4, 2025
985a954
Support serialize/deserialize and `size_in_bits` for external structs
Nov 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions circuit/program/src/data/value/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,24 @@ impl<A: Aleo> Eject for Value<A> {
}
}
}

impl<A: Aleo> From<Plaintext<A>> for Value<A> {
/// Initializes the value from a plaintext.
fn from(plaintext: Plaintext<A>) -> Self {
Self::Plaintext(plaintext)
}
}

impl<A: Aleo> From<Record<A, Plaintext<A>>> for Value<A> {
/// Initializes the value from a record.
fn from(record: Record<A, Plaintext<A>>) -> Self {
Self::Record(record)
}
}

impl<A: Aleo> From<Future<A>> for Value<A> {
/// Initializes the value from a future.
fn from(future: Future<A>) -> Self {
Self::Future(future)
}
}
2 changes: 1 addition & 1 deletion console/network/src/consensus_heights.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub enum ConsensusVersion {
V8 = 8,
/// V9: Support for program upgradability.
V9 = 9,
/// V10: Support for external records.
/// V10: Support for external structs.
V10 = 10,
}

Expand Down
11 changes: 8 additions & 3 deletions console/program/src/data_types/array_type/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// limitations under the License.

use super::*;
use crate::{Identifier, LiteralType};
use crate::{Identifier, LiteralType, Locator};

impl<N: Network> FromBytes for ArrayType<N> {
fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
Expand All @@ -23,7 +23,8 @@ impl<N: Network> FromBytes for ArrayType<N> {
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.
Expand Down Expand Up @@ -58,7 +59,7 @@ impl<N: Network> ToBytes for ArrayType<N> {
// 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()
Expand All @@ -81,6 +82,10 @@ impl<N: Network> ToBytes for ArrayType<N> {
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.
Expand Down
9 changes: 7 additions & 2 deletions console/program/src/data_types/array_type/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,20 @@
// limitations under the License.

use super::*;
use crate::{Identifier, LiteralType};
use crate::{Identifier, LiteralType, Locator};

impl<N: Network> Parser for ArrayType<N> {
/// Parses a string into a literal type.
#[inline]
fn parse(string: &str) -> ParserResult<Self> {
// A helper function to parse the innermost element type.
fn parse_inner_element_type<N: Network>(string: &str) -> ParserResult<PlaintextType<N>> {
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.
Expand Down
7 changes: 6 additions & 1 deletion console/program/src/data_types/plaintext_type/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ impl<N: Network> FromBytes for PlaintextType<N> {
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}"))),
}
}
}
Expand All @@ -44,6 +45,10 @@ impl<N: Network> ToBytes for PlaintextType<N> {
2u8.write_le(&mut writer)?;
array_type.write_le(&mut writer)
}
Self::ExternalStruct(locator) => {
3u8.write_le(&mut writer)?;
locator.write_le(&mut writer)
}
}
}
}
39 changes: 37 additions & 2 deletions console/program/src/data_types/plaintext_type/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ mod bytes;
mod parse;
mod serialize;

use crate::{ArrayType, Identifier, LiteralType};
use crate::{ArrayType, Identifier, LiteralType, Locator, ProgramID};
use snarkvm_console_network::prelude::*;

/// A `PlaintextType` defines the type parameter for a literal, struct, or array.
Expand All @@ -26,12 +26,40 @@ pub enum PlaintextType<N: Network> {
/// A literal type contains its type name.
/// The format of the type is `<type_name>`.
Literal(LiteralType),
/// An struct type contains its identifier.
/// A struct type contains its identifier.
/// The format of the type is `<identifier>`.
Struct(Identifier<N>),
/// An array type contains its element type and length.
/// The format of the type is `[<element_type>; <length>]`.
Array(ArrayType<N>),
/// An external struct type contains its locator.
/// The format of the type is `<program_id>/<identifier>`.
ExternalStruct(Locator<N>),
}

impl<N: Network> PlaintextType<N> {
/// 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<N>) -> 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<N: Network> From<LiteralType> for PlaintextType<N> {
Expand All @@ -48,6 +76,13 @@ impl<N: Network> From<Identifier<N>> for PlaintextType<N> {
}
}

impl<N: Network> From<Locator<N>> for PlaintextType<N> {
/// Initializes a plaintext type from an external struct type.
fn from(locator: Locator<N>) -> Self {
PlaintextType::ExternalStruct(locator)
}
}

impl<N: Network> From<ArrayType<N>> for PlaintextType<N> {
/// Initializes a plaintext type from an array type.
fn from(array: ArrayType<N>) -> Self {
Expand Down
4 changes: 4 additions & 0 deletions console/program/src/data_types/plaintext_type/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ impl<N: Network> Parser for PlaintextType<N> {
fn parse(string: &str) -> ParserResult<Self> {
// 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)
Expand Down Expand Up @@ -60,6 +62,8 @@ impl<N: Network> Display for PlaintextType<N> {
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),
}
Expand Down
18 changes: 17 additions & 1 deletion console/program/src/data_types/register_type/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ mod bytes;
mod parse;
mod serialize;

use crate::{FinalizeType, Identifier, Locator, PlaintextType, ValueType};
use crate::{FinalizeType, Identifier, Locator, PlaintextType, ProgramID, ValueType};
use snarkvm_console_network::prelude::*;

use enum_index::EnumIndex;
Expand All @@ -34,6 +34,22 @@ pub enum RegisterType<N: Network> {
Future(Locator<N>),
}

impl<N: Network> RegisterType<N> {
// Make unqualified structs or records into external ones with the given `id`.
pub fn qualify(self, id: ProgramID<N>) -> 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<N: Network> From<ValueType<N>> for RegisterType<N> {
/// Converts a value type to a register type.
fn from(value: ValueType<N>) -> Self {
Expand Down
9 changes: 9 additions & 0 deletions console/program/src/data_types/value_type/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,15 @@ impl<N: Network> ValueType<N> {
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<N: Network> From<EntryType<N>> for ValueType<N> {
Expand Down
16 changes: 13 additions & 3 deletions synthesizer/process/src/cost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,10 @@ fn plaintext_size_in_bytes<N: Network>(stack: &Stack<N>, 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;
Expand Down Expand Up @@ -267,7 +271,9 @@ pub fn cost_per_command<N: Network>(
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"),
}
}
Expand Down Expand Up @@ -344,7 +350,9 @@ pub fn cost_per_command<N: Network>(
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"),
}
}
Expand All @@ -362,7 +370,9 @@ pub fn cost_per_command<N: Network>(
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"),
}
}
Expand Down
Loading