Skip to content
Open
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1324,7 +1324,7 @@ workflows:
or pipeline.git.branch == "canary"
or pipeline.git.branch == "testnet"
or pipeline.git.branch == "mainnet"
or pipeline.git.branch == "fix/translated_type_tracking"
or pipeline.git.branch == "fix/external_struct_stacks"
jobs:
- check-unused-dependencies # This can be cleaned up before releases
- check-cargo-semver-checks # This can be cleaned up before releases
Expand Down
13 changes: 9 additions & 4 deletions synthesizer/process/src/stack/finalize_types/initialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,12 @@ impl<N: Network> FinalizeTypes<N> {

impl<N: Network> FinalizeTypes<N> {
/// Ensure the given input register is well-formed.
fn check_input(&mut self, stack: &Stack<N>, register: &Register<N>, finalize_type: &FinalizeType<N>) -> Result<()> {
pub fn check_input(
&mut self,
stack: &Stack<N>,
register: &Register<N>,
finalize_type: &FinalizeType<N>,
) -> Result<()> {
// Ensure the register type is defined in the program.
match finalize_type {
FinalizeType::Plaintext(plaintext_type) => RegisterTypes::check_plaintext_type(stack, plaintext_type)?,
Expand All @@ -226,7 +231,7 @@ impl<N: Network> FinalizeTypes<N> {

/// Ensures the given command is well-formed.
#[inline]
fn check_command(
pub fn check_command(
&mut self,
stack: &Stack<N>,
positions: &HashMap<Identifier<N>, usize>,
Expand Down Expand Up @@ -986,7 +991,7 @@ impl<N: Network> FinalizeTypes<N> {
// 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_)?;
self.matches_struct(stack, stack, instruction.operands(), struct_)?;
}
CastType::Plaintext(plaintext @ PlaintextType::ExternalStruct(locator)) => {
// Ensure that the type is valid.
Expand All @@ -996,7 +1001,7 @@ impl<N: Network> FinalizeTypes<N> {
// 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_)?;
self.matches_struct(stack, &*external_stack, instruction.operands(), struct_)?;
}
CastType::Plaintext(plaintext @ PlaintextType::Array(array_type)) => {
// Ensure that the type is valid.
Expand Down
36 changes: 23 additions & 13 deletions synthesizer/process/src/stack/finalize_types/matches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,15 @@ use super::*;

impl<N: Network> FinalizeTypes<N> {
/// Checks that the given operands matches the layout of the struct. The ordering of the operands matters.
pub fn matches_struct(&self, stack: &Stack<N>, operands: &[Operand<N>], struct_: &StructType<N>) -> Result<()> {
pub fn matches_struct(
&self,
// The stack of the program containing the instruction which casts the struct
instruction_stack: &Stack<N>,
// The stack of the program where the struct is defined (as local)
definition_stack: &Stack<N>,
operands: &[Operand<N>],
struct_: &StructType<N>,
) -> Result<()> {
// Retrieve the struct name.
let struct_name = struct_.name();
// Ensure the struct name is valid.
Expand Down Expand Up @@ -52,20 +60,20 @@ impl<N: Network> FinalizeTypes<N> {
}
// Ensure the type of the register matches the member type.
Operand::Register(register) => {
// Retrieve the type.
let plaintext_type = match self.get_type(stack, register)? {
// If the register is a plaintext type, return it.
FinalizeType::Plaintext(plaintext_type) => plaintext_type,
// If the register is a future, throw an error.
// Operate depending on the register type.
match self.get_type(instruction_stack, register)? {
// Ensure the register type is not a future.
FinalizeType::Future(..) => bail!("Struct member cannot be a future"),
// If the register is a dynamic future, throw an error.
// Ensure the register type is not a dynamic future.
FinalizeType::DynamicFuture => bail!("Struct member cannot be a dynamic future"),
// Ensure the plaintext register type matches the member type.
FinalizeType::Plaintext(plaintext_type) => {
ensure!(
types_equivalent(instruction_stack, &plaintext_type, definition_stack, member_type)?,
"Struct member '{struct_name}.{member_name}' expects a '{member_type}', but found a '{plaintext_type}' in the operand '{operand}'.",
)
}
};
// Ensure the register type matches the member type.
ensure!(
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}'.",
)
}
// Ensure the program ID, block height, block timestamp, network ID, generator, checksum, edition, program owner, and component checksum types matches the member type.
Operand::ProgramID(..)
Expand All @@ -79,7 +87,9 @@ impl<N: Network> FinalizeTypes<N> {
| Operand::ProgramOwner(_)
| Operand::ComponentChecksum(..) => {
// Retrieve the operand type.
let FinalizeType::Plaintext(program_ref_type) = self.get_type_from_operand(stack, operand)? else {
let FinalizeType::Plaintext(program_ref_type) =
self.get_type_from_operand(instruction_stack, operand)?
else {
bail!(
"Expected a plaintext type for the operand '{operand}' in struct member '{struct_name}.{member_name}'"
)
Expand Down
6 changes: 6 additions & 0 deletions synthesizer/process/src/stack/finalize_types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ pub struct FinalizeTypes<N: Network> {
}

impl<N: Network> FinalizeTypes<N> {
/// Initializes a new empty instance of `FinalizeTypes`.
#[inline]
pub fn new() -> Self {
Self { inputs: IndexMap::new(), destinations: IndexMap::new() }
}

/// Initializes a new instance of `FinalizeTypes` for the given constructor.
/// Checks that the given constructor is well-formed for the given stack.
#[inline]
Expand Down
23 changes: 14 additions & 9 deletions synthesizer/process/src/stack/register_types/matches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ impl<N: Network> RegisterTypes<N> {
/// Checks that the given operands matches the layout of the struct. The ordering of the operands matters.
pub fn matches_struct(
&self,
operands_stack: &Stack<N>,
stack: &Stack<N>,
// The stack of the program containing the instruction which casts the struct
instruction_stack: &Stack<N>,
// The stack of the program where the struct is defined (as local)
definition_stack: &Stack<N>,
operands: &[Operand<N>],
struct_: &StructType<N>,
) -> Result<()> {
Expand Down Expand Up @@ -51,14 +53,15 @@ impl<N: Network> RegisterTypes<N> {
// 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.",
)
}
// Ensure the register type matches the member type.
Operand::Register(register) => {
// Retrieve the register type.
match self.get_type(operands_stack, register)? {
// Operate depending on the register type.
match self.get_type(instruction_stack, register)? {
// Ensure the register type is not a record.
RegisterType::ExternalRecord(..) | RegisterType::Record(..) => {
bail!("Casting a record into a struct entry is illegal")
Expand All @@ -76,25 +79,27 @@ impl<N: Network> RegisterTypes<N> {
bail!("Casting a dynamic future into a struct entry is illegal")
}
// Ensure the register type matches the member type.
RegisterType::Plaintext(type_) => {
RegisterType::Plaintext(plaintext_type) => {
ensure!(
types_equivalent(operands_stack, &type_, stack, member_type)?,
"Struct entry '{struct_name}.{member_name}' expects a '{member_type}', but found '{type_}' in the operand '{operand}'.",
types_equivalent(instruction_stack, &plaintext_type, definition_stack, member_type)?,
"Struct member '{struct_name}.{member_name}' expects a '{member_type}', but found a '{plaintext_type}' in the operand '{operand}'.",
)
}
}
}
// Ensure the program ID, signer, and caller types match the member type.
Operand::ProgramID(..) | Operand::Signer | Operand::Caller => {
// Retrieve the operand type.
let RegisterType::Plaintext(operand_type) = self.get_type_from_operand(stack, operand)? else {
let RegisterType::Plaintext(operand_type) =
self.get_type_from_operand(instruction_stack, operand)?
else {
bail!(
"Expected a plaintext type for the operand '{operand}' in struct member '{struct_name}.{member_name}'"
)
};
// Ensure the operand type matches the member type.
ensure!(
types_equivalent(stack, &operand_type, stack, member_type)?,
types_equivalent(instruction_stack, &operand_type, definition_stack, member_type)?,
"Struct member '{struct_name}.{member_name}' expects {member_type}, but found '{operand_type}' in the operand '{operand}'.",
)
}
Expand Down
19 changes: 19 additions & 0 deletions synthesizer/program/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1060,6 +1060,25 @@ impl<N: Network> ProgramCore<N> {
|| self.views.values().any(|view| view.contains_external_struct())
}

/// Returns `true` if any of the finalize-type (finalize, constructor and view) contains a cast
/// to an external struct.
#[inline]
pub fn finalize_casts_external_struct(&self) -> bool {
// Check every finalize-type scope: function finalize blocks, the constructor, and views.
self.functions
.values()
.filter_map(|function| function.finalize_logic())
.flat_map(|finalize| finalize.commands())
.chain(self.constructor.iter().flat_map(|constructor| constructor.commands()))
.chain(self.views.values().flat_map(|view| view.commands()))
.any(|command| {
matches!(command, Command::Instruction(instruction)
if matches!(instruction, Instruction::Cast(cast)
if matches!(cast.cast_type(), CastType::Plaintext(PlaintextType::ExternalStruct(..))
)))
})
}

/// Returns `true` if this program violates pre-V13 rules for external records
/// or futures by containing registers with non-local struct types across program boundaries.
///
Expand Down
4 changes: 4 additions & 0 deletions synthesizer/program/src/traits/stack_and_registers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,9 +238,13 @@ pub fn register_types_equivalent<N: Network>(
/// The stacks are passed because struct types need to access their stack to get their
/// structure.
pub fn types_equivalent<N: Network>(
// The first stack
stack0: &impl StackTrait<N>,
// The first type (from the perspective of the first stack)
type0: &PlaintextType<N>,
// The second stack
stack1: &impl StackTrait<N>,
// The second type (from the perspective of the second stack)
type1: &PlaintextType<N>,
) -> Result<bool> {
use PlaintextType::*;
Expand Down
Loading