diff --git a/ledger/src/tests.rs b/ledger/src/tests.rs index ee239d8d2b..0d2e4f6467 100644 --- a/ledger/src/tests.rs +++ b/ledger/src/tests.rs @@ -23,6 +23,7 @@ use console::{ account::{Address, PrivateKey}, network::prelude::*, program::{Entry, Identifier, Literal, Plaintext, ProgramID, Value}, + types::U16, }; use ledger_block::{ConfirmedTransaction, Ratify, Rejected, Transaction}; use ledger_committee::{Committee, MIN_VALIDATOR_STAKE}; @@ -1824,6 +1825,81 @@ finalize foo: } } +#[test] +fn test_metadata() { + let rng = &mut TestRng::default(); + + // Initialize the test environment. + let crate::test_helpers::TestEnv { ledger, private_key, .. } = crate::test_helpers::sample_test_env(rng); + + // Deploy a test program to the ledger. + let program_id = ProgramID::::from_str("metadata.aleo").unwrap(); + let program = Program::::from_str(&format!( + " +program {program_id}; +function is_block: + input r0 as u32.public; + async is_block r0 into r1; + output r1 as {program_id}/is_block.future; + +finalize is_block: + input r0 as u32.public; + assert.eq r0 block.height; + +function is_id: + input r0 as u16.public; + async is_id r0 into r1; + output r1 as {program_id}/is_id.future; + +finalize is_id: + input r0 as u16.public; + assert.eq r0 network.id; + ", + )) + .unwrap(); + + // Deploy. + let transaction = ledger.vm.deploy(&private_key, &program, None, 0, None, rng).unwrap(); + // Verify. + ledger.vm().check_transaction(&transaction, None, rng).unwrap(); + + // Construct the next block. + let block = + ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transaction], rng).unwrap(); + // Advance to the next block. + ledger.advance_to_next_block(&block).unwrap(); + assert_eq!(ledger.latest_height(), 1); + assert_eq!(ledger.latest_hash(), block.hash()); + assert_eq!(program, ledger.get_program(program_id).unwrap()); + + // Execute functions `is_block` and `is_id` to assert that the on-chain state is as expected. + let inputs_block: [Value; 1] = [Value::from_str("2u32").unwrap()]; + let tx_block = + ledger.vm.execute(&private_key, (&program_id, "is_block"), inputs_block.iter(), None, 0, None, rng).unwrap(); + let inputs_id: [Value; 1] = [Value::from(Literal::U16(U16::new(CurrentNetwork::ID)))]; + let tx_id = ledger.vm.execute(&private_key, (&program_id, "is_id"), inputs_id.iter(), None, 0, None, rng).unwrap(); + + // Construct the next block. + let block_2 = + ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![tx_id, tx_block], rng).unwrap(); + // Advance to the next block. + ledger.advance_to_next_block(&block_2).unwrap(); + + // Execute the program. + let inputs_block_2: [Value; 1] = [Value::from_str("3u32").unwrap()]; + let tx_block_2 = + ledger.vm.execute(&private_key, (&program_id, "is_block"), inputs_block_2.iter(), None, 0, None, rng).unwrap(); + let tx_id_2 = + ledger.vm.execute(&private_key, (&program_id, "is_id"), inputs_id.iter(), None, 0, None, rng).unwrap(); + + // Construct the next block. + let block_3 = ledger + .prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![tx_block_2, tx_id_2], rng) + .unwrap(); + // Advance to the next block. + ledger.advance_to_next_block(&block_3).unwrap(); +} + // These tests require the proof targets to be low enough to be able to generate **valid** solutions. // This requires the 'test' feature to be enabled for the `console` dependency. #[cfg(feature = "test")] diff --git a/synthesizer/process/src/stack/evaluate.rs b/synthesizer/process/src/stack/evaluate.rs index d00aaa8a11..078a33931d 100644 --- a/synthesizer/process/src/stack/evaluate.rs +++ b/synthesizer/process/src/stack/evaluate.rs @@ -82,6 +82,8 @@ impl StackEvaluate for Stack { Operand::Caller => Ok(Value::Plaintext(Plaintext::from(Literal::Address(registers.caller()?)))), // If the operand is the block height, throw an error. Operand::BlockHeight => bail!("Cannot retrieve the block height from a closure scope."), + // If the operand is the network id, throw an error. + Operand::NetworkID => bail!("Cannot retrieve the network ID from a closure scope."), } }) .collect(); @@ -213,6 +215,8 @@ impl StackEvaluate for Stack { Operand::Caller => Ok(Value::Plaintext(Plaintext::from(Literal::Address(registers.caller()?)))), // If the operand is the block height, throw an error. Operand::BlockHeight => bail!("Cannot retrieve the block height from a function scope."), + // If the operand is the network id, throw an error. + Operand::NetworkID => bail!("Cannot retrieve the network ID from a function scope."), } }) .collect::>>()?; diff --git a/synthesizer/process/src/stack/execute.rs b/synthesizer/process/src/stack/execute.rs index 4a2936d910..0039eb4f03 100644 --- a/synthesizer/process/src/stack/execute.rs +++ b/synthesizer/process/src/stack/execute.rs @@ -115,6 +115,10 @@ impl StackExecute for Stack { Operand::BlockHeight => { bail!("Illegal operation: cannot retrieve the block height in a closure scope") } + // If the operand is the network id, throw an error. + Operand::NetworkID => { + bail!("Illegal operation: cannot retrieve the network id in a closure scope") + } } }) .collect(); @@ -343,6 +347,10 @@ impl StackExecute for Stack { Operand::BlockHeight => { bail!("Illegal operation: cannot retrieve the block height in a function scope") } + // If the operand is the network id, throw an error. + Operand::NetworkID => { + bail!("Illegal operation: cannot retrieve the network id in a function scope") + } } }) .collect::>>()?; diff --git a/synthesizer/process/src/stack/finalize_registers/load.rs b/synthesizer/process/src/stack/finalize_registers/load.rs index 1a0c1bd4a2..ff161c845e 100644 --- a/synthesizer/process/src/stack/finalize_registers/load.rs +++ b/synthesizer/process/src/stack/finalize_registers/load.rs @@ -41,6 +41,10 @@ impl RegistersLoad for FinalizeRegisters { Operand::BlockHeight => { return Ok(Value::Plaintext(Plaintext::from(Literal::U32(U32::new(self.state.block_height()))))); } + // If the operand is the network ID, load the network ID. + Operand::NetworkID => { + return Ok(Value::Plaintext(Plaintext::from(Literal::U16(U16::new(N::ID))))); + } }; // Retrieve the value. diff --git a/synthesizer/process/src/stack/finalize_registers/mod.rs b/synthesizer/process/src/stack/finalize_registers/mod.rs index cde50b24ec..0758ef6723 100644 --- a/synthesizer/process/src/stack/finalize_registers/mod.rs +++ b/synthesizer/process/src/stack/finalize_registers/mod.rs @@ -19,7 +19,7 @@ use crate::FinalizeTypes; use console::{ network::prelude::*, program::{Identifier, Literal, Plaintext, Register, Value}, - types::U32, + types::{U16, U32}, }; use synthesizer_program::{ FinalizeGlobalState, diff --git a/synthesizer/process/src/stack/finalize_types/matches.rs b/synthesizer/process/src/stack/finalize_types/matches.rs index 5e01d2449b..aa04ace2be 100644 --- a/synthesizer/process/src/stack/finalize_types/matches.rs +++ b/synthesizer/process/src/stack/finalize_types/matches.rs @@ -96,6 +96,16 @@ impl FinalizeTypes { "Struct member '{struct_name}.{member_name}' expects {member_type}, but found '{block_height_type}' in the operand '{operand}'.", ) } + // Ensure the network ID type (u16) matches the member type. + Operand::NetworkID => { + // Retrieve the network ID type. + let network_id_type = PlaintextType::Literal(LiteralType::U16); + // Ensure the network ID type matches the member type. + ensure!( + &network_id_type == member_type, + "Struct member '{struct_name}.{member_name}' expects {member_type}, but found '{network_id_type}' in the operand '{operand}'.", + ) + } } } Ok(()) @@ -177,6 +187,17 @@ impl FinalizeTypes { array_type.next_element_type() ) } + // Ensure the network ID type (u16) matches the member type. + Operand::NetworkID => { + // Retrieve the network ID type. + let network_id_type = PlaintextType::Literal(LiteralType::U16); + // Ensure the network ID type matches the member type. + ensure!( + &network_id_type == array_type.next_element_type(), + "Array element expects {}, but found '{network_id_type}' in the operand '{operand}'.", + array_type.next_element_type() + ) + } } } Ok(()) diff --git a/synthesizer/process/src/stack/finalize_types/mod.rs b/synthesizer/process/src/stack/finalize_types/mod.rs index 7b5e7c957d..717e3aa496 100644 --- a/synthesizer/process/src/stack/finalize_types/mod.rs +++ b/synthesizer/process/src/stack/finalize_types/mod.rs @@ -103,6 +103,7 @@ impl FinalizeTypes { Operand::Signer => bail!("'self.signer' is not a valid operand in a finalize context."), Operand::Caller => bail!("'self.caller' is not a valid operand in a finalize context."), Operand::BlockHeight => FinalizeType::Plaintext(PlaintextType::Literal(LiteralType::U32)), + Operand::NetworkID => FinalizeType::Plaintext(PlaintextType::Literal(LiteralType::U16)), }) } diff --git a/synthesizer/process/src/stack/register_types/matches.rs b/synthesizer/process/src/stack/register_types/matches.rs index 3957726841..6c6a9bc94d 100644 --- a/synthesizer/process/src/stack/register_types/matches.rs +++ b/synthesizer/process/src/stack/register_types/matches.rs @@ -88,6 +88,10 @@ impl RegisterTypes { Operand::BlockHeight => bail!( "Struct member '{struct_name}.{member_name}' cannot be from a block height in a non-finalize scope" ), + // If the operand is a network ID type, throw an error. + Operand::NetworkID => bail!( + "Struct member '{struct_name}.{member_name}' cannot be from a network ID in a non-finalize scope" + ), } } Ok(()) @@ -162,6 +166,8 @@ impl RegisterTypes { } // If the operand is a block height type, throw an error. Operand::BlockHeight => bail!("Array element cannot be from a block height in a non-finalize scope"), + // If the operand is a network ID type, throw an error. + Operand::NetworkID => bail!("Array element cannot be from a network ID in a non-finalize scope"), } } Ok(()) @@ -224,6 +230,9 @@ impl RegisterTypes { Operand::BlockHeight => { bail!("Forbidden operation: Cannot cast a block height as a record owner") } + Operand::NetworkID => { + bail!("Forbidden operation: Cannot cast a network ID as a record owner") + } } // Ensure the operand types match the record entry types. @@ -279,6 +288,12 @@ impl RegisterTypes { "Record entry '{record_name}.{entry_name}' expects a '{plaintext_type}', but found a block height in the operand '{operand}'." ) } + // Fail if the operand is a network ID. + Operand::NetworkID => { + bail!( + "Record entry '{record_name}.{entry_name}' expects a '{plaintext_type}', but found a network ID 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 558436d2d4..94479b895c 100644 --- a/synthesizer/process/src/stack/register_types/mod.rs +++ b/synthesizer/process/src/stack/register_types/mod.rs @@ -98,6 +98,7 @@ impl RegisterTypes { RegisterType::Plaintext(PlaintextType::Literal(LiteralType::Address)) } Operand::BlockHeight => bail!("'block.height' is not a valid operand in a non-finalize context."), + Operand::NetworkID => bail!("'network.id' is not a valid operand in a non-finalize context."), }) } diff --git a/synthesizer/process/src/stack/registers/load.rs b/synthesizer/process/src/stack/registers/load.rs index f9e1e1f875..e9b43762cc 100644 --- a/synthesizer/process/src/stack/registers/load.rs +++ b/synthesizer/process/src/stack/registers/load.rs @@ -38,6 +38,8 @@ impl> RegistersLoad for Registers return Ok(Value::Plaintext(Plaintext::from(Literal::Address(self.caller()?)))), // If the operand is the block height, throw an error. Operand::BlockHeight => bail!("Cannot load the block height in a non-finalize context"), + // If the operand is the network ID, throw an error. + Operand::NetworkID => bail!("Cannot load the network ID in a non-finalize context"), }; // Retrieve the stack value. @@ -121,6 +123,8 @@ impl> RegistersLoadCircuit for R } // If the operand is the block height, throw an error. Operand::BlockHeight => bail!("Cannot load the block height in a non-finalize context"), + // If the operand is the network ID, throw an error. + Operand::NetworkID => bail!("Cannot load the network ID in a non-finalize context"), }; // Retrieve the circuit value. diff --git a/synthesizer/program/src/logic/instruction/operand/bytes.rs b/synthesizer/program/src/logic/instruction/operand/bytes.rs index 729fd26090..8e88c72fad 100644 --- a/synthesizer/program/src/logic/instruction/operand/bytes.rs +++ b/synthesizer/program/src/logic/instruction/operand/bytes.rs @@ -23,6 +23,7 @@ impl FromBytes for Operand { 3 => Ok(Self::Signer), 4 => Ok(Self::Caller), 5 => Ok(Self::BlockHeight), + 6 => Ok(Self::NetworkID), variant => Err(error(format!("Failed to deserialize operand variant {variant}"))), } } @@ -46,6 +47,7 @@ impl ToBytes for Operand { Self::Signer => 3u8.write_le(&mut writer), Self::Caller => 4u8.write_le(&mut writer), Self::BlockHeight => 5u8.write_le(&mut writer), + Self::NetworkID => 6u8.write_le(&mut writer), } } } diff --git a/synthesizer/program/src/logic/instruction/operand/mod.rs b/synthesizer/program/src/logic/instruction/operand/mod.rs index e0b503e4fc..29110a1618 100644 --- a/synthesizer/program/src/logic/instruction/operand/mod.rs +++ b/synthesizer/program/src/logic/instruction/operand/mod.rs @@ -40,6 +40,9 @@ pub enum Operand { /// The operand is the block height. /// Note: This variant is only accessible in the `finalize` scope. BlockHeight, + /// The operand is the network ID. + /// Note: This variant is only accessible in the `finalize` scope. + NetworkID, } impl From> for Operand { diff --git a/synthesizer/program/src/logic/instruction/operand/parse.rs b/synthesizer/program/src/logic/instruction/operand/parse.rs index 7c54e2cd6b..cf6c76f37b 100644 --- a/synthesizer/program/src/logic/instruction/operand/parse.rs +++ b/synthesizer/program/src/logic/instruction/operand/parse.rs @@ -26,6 +26,7 @@ impl Parser for Operand { map(tag("self.signer"), |_| Self::Signer), map(tag("self.caller"), |_| Self::Caller), map(tag("block.height"), |_| Self::BlockHeight), + map(tag("network.id"), |_| Self::NetworkID), // Note that `Operand::ProgramID`s must be parsed before `Operand::Literal`s, since a program ID can be implicitly parsed as a literal address. // This ensures that the string representation of a program uses the `Operand::ProgramID` variant. map(ProgramID::parse, |program_id| Self::ProgramID(program_id)), @@ -76,6 +77,8 @@ impl Display for Operand { Self::Caller => write!(f, "self.caller"), // Prints the identifier for the block height, i.e. block.height Self::BlockHeight => write!(f, "block.height"), + // Prints the identifier for the network ID, i.e. network.id + Self::NetworkID => write!(f, "network.id"), } } } @@ -110,6 +113,9 @@ mod tests { let operand = Operand::::parse("block.height").unwrap().1; assert_eq!(Operand::BlockHeight, operand); + let operand = Operand::::parse("network.id").unwrap().1; + assert_eq!(Operand::NetworkID, operand); + let operand = Operand::::parse("group::GEN").unwrap().1; assert_eq!(Operand::Literal(Literal::Group(Group::generator())), operand); diff --git a/synthesizer/tests/expectations/parser/instruction/instruction_pass.out b/synthesizer/tests/expectations/parser/instruction/instruction_pass.out index 7de1861e4b..add2fafbe9 100644 --- a/synthesizer/tests/expectations/parser/instruction/instruction_pass.out +++ b/synthesizer/tests/expectations/parser/instruction/instruction_pass.out @@ -62,3 +62,4 @@ - Parsing was successful. - Parsing was successful. - Parsing was successful. +- Parsing was successful. diff --git a/synthesizer/tests/expectations/parser/instruction/operand_pass.out b/synthesizer/tests/expectations/parser/instruction/operand_pass.out index df4a409fbc..ca15d75f4c 100644 --- a/synthesizer/tests/expectations/parser/instruction/operand_pass.out +++ b/synthesizer/tests/expectations/parser/instruction/operand_pass.out @@ -18,3 +18,4 @@ - Parsing was successful. - Parsing was successful. - Parsing was successful. +- Parsing was successful. diff --git a/synthesizer/tests/tests/parser/instruction/instruction_pass.aleo b/synthesizer/tests/tests/parser/instruction/instruction_pass.aleo index f5d2aafa1e..f0df1195e9 100644 --- a/synthesizer/tests/tests/parser/instruction/instruction_pass.aleo +++ b/synthesizer/tests/tests/parser/instruction/instruction_pass.aleo @@ -7,6 +7,7 @@ and r0 r1 into r2; assert.eq r0 r1; assert.neq r0 r1; assert.eq block.height block.height; +assert.eq network.id network.id; call foo; call foo r0; call foo r0 into r1; diff --git a/synthesizer/tests/tests/parser/instruction/operand_pass.aleo b/synthesizer/tests/tests/parser/instruction/operand_pass.aleo index 4009a2eb87..14c0ff455e 100644 --- a/synthesizer/tests/tests/parser/instruction/operand_pass.aleo +++ b/synthesizer/tests/tests/parser/instruction/operand_pass.aleo @@ -1,5 +1,6 @@ assert.eq self.caller self.caller; assert.eq block.height block.height; +assert.eq network.id network.id; assert.eq r88 r101; assert.eq hello.aleo goodbye.aleo; assert.eq aleo1dg722m22fzpz6xjdrvl9tzu5t68zmypj5p74khlqcac0gvednygqxaax0j aleo1dg722m22fzpz6xjdrvl9tzu5t68zmypj5p74khlqcac0gvednygqxaax0j;