diff --git a/Cargo.lock b/Cargo.lock index af974b78bd..882a8463b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3159,6 +3159,7 @@ name = "snarkvm-console-network" version = "4.0.0" dependencies = [ "anyhow", + "enum-iterator", "indexmap 2.10.0", "lazy_static", "paste", @@ -3745,6 +3746,7 @@ dependencies = [ "snarkvm-synthesizer-process", "snarkvm-synthesizer-snark", "snarkvm-utilities", + "tiny-keccak", ] [[package]] diff --git a/circuit/program/src/data/plaintext/mod.rs b/circuit/program/src/data/plaintext/mod.rs index 54dcb7a4d4..25fedfebd1 100644 --- a/circuit/program/src/data/plaintext/mod.rs +++ b/circuit/program/src/data/plaintext/mod.rs @@ -99,6 +99,31 @@ impl From<&Literal> for Plaintext { } } +// A macro that derives implementations of `From` for arrays of a plaintext literals of various sizes. +macro_rules! impl_plaintext_from_array { + ($element:ident, $($size:literal),+) => { + $( + impl From<[$element; $size]> for Plaintext { + fn from(value: [$element; $size]) -> Self { + Self::Array( + value + .into_iter() + .map(|element| Plaintext::from(Literal::$element(element))) + .collect(), + OnceCell::new(), + ) + } + } + )+ + }; +} + +// Implement for `[U8, SIZE]` for sizes 1 through 32. +impl_plaintext_from_array!( + U8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, + 31, 32 +); + #[cfg(test)] mod tests { use super::*; diff --git a/circuit/program/src/request/verify.rs b/circuit/program/src/request/verify.rs index 406c399e4f..15f3cabdd6 100644 --- a/circuit/program/src/request/verify.rs +++ b/circuit/program/src/request/verify.rs @@ -20,13 +20,15 @@ impl Request { /// and the signature is valid. /// /// Verifies (challenge == challenge') && (address == address') && (serial_numbers == serial_numbers') where: - /// challenge' := HashToScalar(r * G, pk_sig, pr_sig, signer, \[tvk, tcm, function ID, input IDs\]) + /// challenge' := HashToScalar(r * G, pk_sig, pr_sig, signer, \[tvk, tcm, function ID, is_root, program checksum?, input IDs\]) + /// The program checksum must be provided if the program has a constructor and should not be provided otherwise. pub fn verify( &self, input_types: &[console::ValueType], tpk: &Group, root_tvk: Option>, is_root: Boolean, + program_checksum: Option>, ) -> Boolean { // Compute the function ID. let function_id = compute_function_id(&self.network_id, &self.program_id, &self.function_name); @@ -40,6 +42,10 @@ impl Request { message.push(self.tcm.clone()); message.push(function_id); message.push(is_root); + // Add the program checksum to the signature message if it was provided. + if let Some(program_checksum) = program_checksum { + message.push(program_checksum); + } // Check the input IDs and construct the rest of the signature message. let (input_checks, append_to_message) = Self::check_input_ids::( @@ -61,6 +67,7 @@ impl Request { None => A::halt("Missing input elements in request verification"), } + // Determine the root transition view key. let root_tvk = root_tvk.unwrap_or(Field::::new(Mode::Private, self.tvk.eject_value())); // Verify the transition public key and commitments are well-formed. @@ -324,6 +331,7 @@ mod tests { num_public: u64, num_private: u64, num_constraints: u64, + set_program_checksum: bool, ) -> Result<()> { let rng = &mut TestRng::default(); @@ -367,9 +375,10 @@ mod tests { // Sample 'root_tvk'. let root_tvk = None; - // Sample 'is_root'. let is_root = true; + // Sample 'program_checksum'. + let program_checksum = set_program_checksum.then(|| console::Field::from_u64(i as u64)); // Compute the signed request. let request = console::Request::sign( @@ -380,18 +389,20 @@ mod tests { &input_types, root_tvk, is_root, + program_checksum, rng, )?; - assert!(request.verify(&input_types, is_root)); + assert!(request.verify(&input_types, is_root, program_checksum)); // Inject the request into a circuit. let tpk = Group::::new(mode, request.to_tpk()); let request = Request::::new(mode, request); let is_root = Boolean::new(mode, is_root); + let program_checksum = program_checksum.map(|hash| Field::::new(mode, hash)); Circuit::scope(format!("Request {i}"), || { let root_tvk = None; - let candidate = request.verify(&input_types, &tpk, root_tvk, is_root); + let candidate = request.verify(&input_types, &tpk, root_tvk, is_root, program_checksum); assert!(candidate.eject_value()); match mode.is_constant() { true => assert_scope!(<=num_constants, <=num_public, <=num_private, <=num_constraints), @@ -425,16 +436,19 @@ mod tests { // Note: This is correct. At this (high) level of a program, we override the default mode in the `Record` case, // based on the user-defined visibility in the record type. Thus, we have nonzero private and constraint values. // These bounds are determined experimentally. - check_verify(Mode::Constant, 45000, 0, 22000, 22000) + check_verify(Mode::Constant, 43440, 0, 21629, 21656, false)?; + check_verify(Mode::Constant, 43440, 0, 21629, 21656, true) } #[test] fn test_sign_and_verify_public() -> Result<()> { - check_verify(Mode::Public, 40938, 0, 30031, 30062) + check_verify(Mode::Public, 40938, 0, 30031, 30062, false)?; + check_verify(Mode::Public, 40938, 0, 30546, 30577, true) } #[test] fn test_sign_and_verify_private() -> Result<()> { - check_verify(Mode::Private, 40938, 0, 30031, 30062) + check_verify(Mode::Private, 40938, 0, 30031, 30062, false)?; + check_verify(Mode::Private, 40938, 0, 30546, 30577, true) } } diff --git a/console/algorithms/benches/bhp.rs b/console/algorithms/benches/bhp.rs index 4dcc191771..fbd97b8c3a 100644 --- a/console/algorithms/benches/bhp.rs +++ b/console/algorithms/benches/bhp.rs @@ -66,10 +66,30 @@ fn bhp1024(c: &mut Criterion) { c.bench_function(&format!("BHP1024 Hash - input size {}", input.len()), |b| b.iter(|| hash.hash(&input))); } +fn bhp1024_large(c: &mut Criterion) { + const SIZE_IN_BYTES: [usize; 4] = + [1_000 /* 1 kB */, 10_000 /* 10 kB */, 100_000 /* 100 kB */, 1_000_000 /* 1 MB */]; + let rng = &mut TestRng::default(); + + // Benchmark the BHP1024 hash function for different input sizes. + for size in SIZE_IN_BYTES.iter() { + let input = (0..size * 8).map(|_| bool::rand(rng)).collect::>(); + c.bench_function(&format!("BHP1024 Hash - input size {size} bytes"), |b| { + b.iter(|| BHP1024::::setup("BHP1024").unwrap().hash(&input)) + }); + } +} + criterion_group! { name = bhp; config = Criterion::default().sample_size(1000); targets = bhp256, bhp512, bhp768, bhp1024 } -criterion_main!(bhp); +criterion_group! { + name = bhp_large; + config = Criterion::default().sample_size(100); + targets = bhp1024_large +} + +criterion_main!(bhp, bhp_large); diff --git a/console/network/Cargo.toml b/console/network/Cargo.toml index 0470dc10ef..e7ba95df2a 100644 --- a/console/network/Cargo.toml +++ b/console/network/Cargo.toml @@ -41,6 +41,9 @@ workspace = true [dependencies.anyhow] workspace = true +[dependencies.enum-iterator] +version = "2.1" + [dependencies.indexmap] workspace = true diff --git a/console/network/src/canary_v0.rs b/console/network/src/canary_v0.rs index 84c589a240..a1dd052143 100644 --- a/console/network/src/canary_v0.rs +++ b/console/network/src/canary_v0.rs @@ -146,8 +146,6 @@ impl Network for CanaryV0 { /// The transmission checksum type. type TransmissionChecksum = u128; - /// The network edition. - const EDITION: u16 = 0; /// The genesis block coinbase target. #[cfg(not(feature = "test_targets"))] const GENESIS_COINBASE_TARGET: u64 = (1u64 << 29).saturating_sub(1); @@ -185,16 +183,8 @@ impl Network for CanaryV0 { /// A list of (consensus_version, block_height) pairs indicating when each consensus version takes effect. /// Documentation for what is changed at each version can be found in `ConsensusVersion`. /// Do not read this directly outside of tests, use `N::CONSENSUS_VERSION_HEIGHTS()` instead. - const _CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [ - (ConsensusVersion::V1, 0), - (ConsensusVersion::V2, 2_900_000), - (ConsensusVersion::V3, 4_560_000), - (ConsensusVersion::V4, 5_730_000), - (ConsensusVersion::V5, 5_780_000), - (ConsensusVersion::V6, 6_240_000), - (ConsensusVersion::V7, 6_880_000), - (ConsensusVersion::V8, 7_565_000), - ]; + const _CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = + CANARY_V0_CONSENSUS_VERSION_HEIGHTS; /// Returns the block height where the the inclusion proof will be updated. #[allow(non_snake_case)] diff --git a/console/network/src/consensus_heights.rs b/console/network/src/consensus_heights.rs new file mode 100644 index 0000000000..934b91211b --- /dev/null +++ b/console/network/src/consensus_heights.rs @@ -0,0 +1,113 @@ +// 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 crate::ConsensusVersion; + +/// The consensus version height for `CanaryV0`. +pub const CANARY_V0_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); 9] = [ + (ConsensusVersion::V1, 0), + (ConsensusVersion::V2, 2_900_000), + (ConsensusVersion::V3, 4_560_000), + (ConsensusVersion::V4, 5_730_000), + (ConsensusVersion::V5, 5_780_000), + (ConsensusVersion::V6, 6_240_000), + (ConsensusVersion::V7, 6_880_000), + (ConsensusVersion::V8, 7_565_000), + (ConsensusVersion::V9, 999_999_999), +]; + +/// The consensus version height for `MainnetV0`. +pub const MAINNET_V0_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); 9] = [ + (ConsensusVersion::V1, 0), + (ConsensusVersion::V2, 2_800_000), + (ConsensusVersion::V3, 4_900_000), + (ConsensusVersion::V4, 6_135_000), + (ConsensusVersion::V5, 7_060_000), + (ConsensusVersion::V6, 7_560_000), + (ConsensusVersion::V7, 7_570_000), + (ConsensusVersion::V8, 9_430_000), + (ConsensusVersion::V9, 999_999_999), +]; + +/// The consensus version heights for `TestnetV0`. +pub const TESTNET_V0_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); 9] = [ + (ConsensusVersion::V1, 0), + (ConsensusVersion::V2, 2_950_000), + (ConsensusVersion::V3, 4_800_000), + (ConsensusVersion::V4, 6_625_000), + (ConsensusVersion::V5, 6_765_000), + (ConsensusVersion::V6, 7_600_000), + (ConsensusVersion::V7, 8_365_000), + (ConsensusVersion::V8, 9_173_000), + (ConsensusVersion::V9, 999_999_999), +]; + +/// The consensus version heights when the `test_consensus_heights` feature is enabled. +pub const TEST_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); 9] = [ + (ConsensusVersion::V1, 0), + (ConsensusVersion::V2, 10), + (ConsensusVersion::V3, 11), + (ConsensusVersion::V4, 12), + (ConsensusVersion::V5, 13), + (ConsensusVersion::V6, 14), + (ConsensusVersion::V7, 15), + (ConsensusVersion::V8, 16), + (ConsensusVersion::V9, 17), +]; + +#[cfg(any(test, feature = "test", feature = "test_consensus_heights"))] +pub(crate) fn load_test_consensus_heights() +-> [(ConsensusVersion, u32); crate::NUM_CONSENSUS_VERSIONS] { + // Define a closure to verify the consensus heights. + let verify_consensus_heights = |heights: &[(ConsensusVersion, u32); crate::NUM_CONSENSUS_VERSIONS]| { + // Assert that the genesis height is 0. + assert_eq!(heights[0].1, 0, "Genesis height must be 0."); + // Assert that the consensus heights are strictly increasing. + for window in heights.windows(2) { + if window[0] >= window[1] { + panic!("Heights must be strictly increasing, but found: {window:?}"); + } + } + }; + + // Define consensus version heights container used for testing. + let mut test_consensus_heights = N::TEST_CONSENSUS_VERSION_HEIGHTS; + + // Check if we can read the heights from an environment variable. + match std::env::var("CONSENSUS_VERSION_HEIGHTS") { + Ok(height_string) => { + // Parse the heights from the environment variable. + let parsed_test_consensus_heights: [u32; crate::NUM_CONSENSUS_VERSIONS] = height_string + .replace(" ", "") + .split(",") + .map(|height| height.parse::().unwrap()) + .collect::>() + .try_into() + .unwrap(); + // Set the parsed heights in the test consensus heights. + for (i, height) in parsed_test_consensus_heights.into_iter().enumerate() { + test_consensus_heights[i] = (N::TEST_CONSENSUS_VERSION_HEIGHTS[i].0, height); + } + // Verify and return the parsed test consensus heights. + verify_consensus_heights(&test_consensus_heights); + test_consensus_heights + } + Err(_) => { + // Verify and return the default test consensus heights. + verify_consensus_heights(&test_consensus_heights); + test_consensus_heights + } + } +} diff --git a/console/network/src/lib.rs b/console/network/src/lib.rs index 256912ba58..8107613628 100644 --- a/console/network/src/lib.rs +++ b/console/network/src/lib.rs @@ -29,14 +29,27 @@ pub use helpers::*; mod canary_v0; pub use canary_v0::*; +mod consensus_heights; +pub use consensus_heights::*; + mod mainnet_v0; pub use mainnet_v0::*; mod testnet_v0; + pub use testnet_v0::*; pub mod prelude { - pub use crate::{ConsensusVersion, Network, consensus_config_value, environment::prelude::*}; + pub use crate::{ + CANARY_V0_CONSENSUS_VERSION_HEIGHTS, + ConsensusVersion, + MAINNET_V0_CONSENSUS_VERSION_HEIGHTS, + Network, + TEST_CONSENSUS_VERSION_HEIGHTS, + TESTNET_V0_CONSENSUS_VERSION_HEIGHTS, + consensus_config_value, + environment::prelude::*, + }; } use crate::environment::prelude::*; @@ -51,6 +64,7 @@ use snarkvm_console_collections::merkle_tree::{MerklePath, MerkleTree}; use snarkvm_console_types::{Field, Group, Scalar}; use snarkvm_curves::PairingEngine; +use enum_iterator::{Sequence, last}; use indexmap::IndexMap; use std::sync::{Arc, OnceLock}; @@ -70,7 +84,7 @@ pub(crate) type VarunaVerifyingKey = CircuitVerifyingKey<:: /// The different consensus versions. /// If you need the version active for a specific height, see: `N::CONSENSUS_VERSION`. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Sequence)] pub enum ConsensusVersion { /// V1: The initial genesis consensus version. V1 = 1, @@ -88,10 +102,18 @@ pub enum ConsensusVersion { V7 = 7, /// V8: Update to inclusion version, record commitment version, and introduces sender ciphertexts. V8 = 8, + /// V9: Support for program upgradability. + V9 = 9, +} + +impl ConsensusVersion { + pub fn latest() -> Self { + last::().unwrap() + } } /// The number of consensus versions. -const NUM_CONSENSUS_VERSIONS: usize = 8; +pub(crate) const NUM_CONSENSUS_VERSIONS: usize = 9; /// A list of consensus versions and their corresponding block heights. static CONSENSUS_VERSION_HEIGHTS: OnceLock<[(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS]> = OnceLock::new(); @@ -114,8 +136,6 @@ pub trait Network: const ID: u16; /// The network name. const NAME: &'static str; - /// The network edition. - const EDITION: u16; /// The function name for the inclusion circuit. const INCLUSION_FUNCTION_NAME: &'static str; @@ -135,6 +155,8 @@ pub trait Network: const STARTING_SUPPLY: u64 = 1_500_000_000_000_000; // 1.5B credits /// The cost in microcredits per byte for the deployment transaction. const DEPLOYMENT_FEE_MULTIPLIER: u64 = 1_000; // 1 millicredit per byte + /// The multiplier in microcredits for each command in the constructor. + const CONSTRUCTOR_FEE_MULTIPLIER: u64 = 100; // 100x per command /// The constant that divides the storage polynomial. const EXECUTION_STORAGE_FEE_SCALING_FACTOR: u64 = 5000; /// The maximum size execution transactions can be before a quadratic storage penalty applies. @@ -147,7 +169,7 @@ pub trait Network: const MAX_DEPLOYMENT_CONSTRAINTS: u64 = 1 << 21; // 2,097,152 constraints /// The maximum number of microcredits that can be spent as a fee. const MAX_FEE: u64 = 1_000_000_000_000_000; - /// The maximum number of microcredits that can be spent on a transaction's finalize scope. + /// The maximum number of microcredits that can be spent on a constructor or finalize scope. const TRANSACTION_SPEND_LIMIT: u64 = 100_000_000; /// The anchor height, defined as the expected number of blocks to reach the coinbase target. @@ -204,6 +226,8 @@ pub trait Network: const MAX_COMMANDS: usize = u16::MAX as usize; /// The maximum number of write commands in finalize. const MAX_WRITES: u16 = 16; + /// The maximum number of `position` commands in finalize. + const MAX_POSITIONS: usize = u8::MAX as usize; /// The maximum number of inputs per transition. const MAX_INPUTS: usize = 16; @@ -233,7 +257,7 @@ pub trait Network: /// A list of (consensus_version, block_height) pairs indicating when each consensus version takes effect. /// Documentation for what is changed at each version can be found in `N::CONSENSUS_VERSION` /// Do not read this directly outside of tests, use `N::CONSENSUS_VERSION_HEIGHTS()` instead. - const _CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); 8]; + const _CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); 9]; /// A list of (consensus_version, size) pairs indicating the maximum number of validators in a committee. // Note: This value must **not** decrease without considering the impact on serialization. @@ -245,7 +269,7 @@ pub trait Network: /// Returns the list of consensus versions. #[allow(non_snake_case)] #[cfg(not(any(test, feature = "test", feature = "test_consensus_heights")))] - fn CONSENSUS_VERSION_HEIGHTS() -> &'static [(ConsensusVersion, u32); 8] { + fn CONSENSUS_VERSION_HEIGHTS() -> &'static [(ConsensusVersion, u32); 9] { // Initialize the consensus version heights directly from the constant. CONSENSUS_VERSION_HEIGHTS.get_or_init(|| Self::_CONSENSUS_VERSION_HEIGHTS) } @@ -254,62 +278,14 @@ pub trait Network: #[cfg(any(test, feature = "test", feature = "test_consensus_heights"))] fn CONSENSUS_VERSION_HEIGHTS() -> &'static [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] { // NOTE: this function may panic, as it is only called during startup. - CONSENSUS_VERSION_HEIGHTS.get_or_init(|| { - // Define a closure to verify the consensus heights. - let verify_consensus_heights = |heights: &[(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS]| { - // Assert that the genesis height is 0. - assert_eq!(heights[0].1, 0, "Genesis height must be 0."); - // Assert that the consensus heights are strictly increasing. - for window in heights.windows(2) { - if window[0] >= window[1] { - panic!("Heights must be strictly increasing, but found: {window:?}"); - } - } - }; - - // Define consensus version heights container used for testing. - let mut test_consensus_heights = Self::TEST_CONSENSUS_VERSION_HEIGHTS; - - // Check if we can read the heights from an environment variable. - match std::env::var("CONSENSUS_VERSION_HEIGHTS") { - Ok(height_string) => { - // Parse the heights from the environment variable. - let parsed_test_consensus_heights: [u32; NUM_CONSENSUS_VERSIONS] = height_string - .replace(" ", "") - .split(",") - .map(|height| height.parse::().unwrap()) - .collect::>() - .try_into() - .unwrap(); - // Set the parsed heights in the test consensus heights. - for (i, height) in parsed_test_consensus_heights.into_iter().enumerate() { - test_consensus_heights[i] = (Self::TEST_CONSENSUS_VERSION_HEIGHTS[i].0, height); - } - // Verify and return the parsed test consensus heights. - verify_consensus_heights(&test_consensus_heights); - test_consensus_heights - } - Err(_) => { - // Verify and return the default test consensus heights. - verify_consensus_heights(&test_consensus_heights); - test_consensus_heights - } - } - }) + CONSENSUS_VERSION_HEIGHTS.get_or_init(|| load_test_consensus_heights::()) } + /// A set of incrementing consensus version heights used for tests. #[allow(non_snake_case)] #[cfg(any(test, feature = "test", feature = "test_consensus_heights"))] - const TEST_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [ - (ConsensusVersion::V1, 0), - (ConsensusVersion::V2, 10), - (ConsensusVersion::V3, 11), - (ConsensusVersion::V4, 12), - (ConsensusVersion::V5, 13), - (ConsensusVersion::V6, 14), - (ConsensusVersion::V7, 15), - (ConsensusVersion::V8, 16), - ]; + const TEST_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = + TEST_CONSENSUS_VERSION_HEIGHTS; /// Returns the consensus version which is active at the given height. #[allow(non_snake_case)] fn CONSENSUS_VERSION(seek_height: u32) -> anyhow::Result { @@ -675,4 +651,9 @@ mod tests { constants_equal_length::(); } + + #[test] + fn test_latest_consensus_version() { + assert_eq!(ConsensusVersion::latest(), ConsensusVersion::V9); // UPDATE ME, if changed. + } } diff --git a/console/network/src/mainnet_v0.rs b/console/network/src/mainnet_v0.rs index b943faa9b0..2c759a3689 100644 --- a/console/network/src/mainnet_v0.rs +++ b/console/network/src/mainnet_v0.rs @@ -147,8 +147,6 @@ impl Network for MainnetV0 { /// The transmission checksum type. type TransmissionChecksum = u128; - /// The network edition. - const EDITION: u16 = 0; /// The genesis block coinbase target. #[cfg(not(feature = "test"))] const GENESIS_COINBASE_TARGET: u64 = (1u64 << 29).saturating_sub(1); @@ -190,16 +188,8 @@ impl Network for MainnetV0 { /// A list of (consensus_version, block_height) pairs indicating when each consensus version takes effect. /// Documentation for what is changed at each version can be found in `ConsensusVersion`. /// Do not read this directly outside of tests, use `N::CONSENSUS_VERSION_HEIGHTS()` instead. - const _CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [ - (ConsensusVersion::V1, 0), - (ConsensusVersion::V2, 2_800_000), - (ConsensusVersion::V3, 4_900_000), - (ConsensusVersion::V4, 6_135_000), - (ConsensusVersion::V5, 7_060_000), - (ConsensusVersion::V6, 7_560_000), - (ConsensusVersion::V7, 7_570_000), - (ConsensusVersion::V8, 9_430_000), - ]; + const _CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = + MAINNET_V0_CONSENSUS_VERSION_HEIGHTS; /// Returns the block height where the the inclusion proof will be updated. #[allow(non_snake_case)] diff --git a/console/network/src/testnet_v0.rs b/console/network/src/testnet_v0.rs index 90860fc319..f42fd20d8b 100644 --- a/console/network/src/testnet_v0.rs +++ b/console/network/src/testnet_v0.rs @@ -146,8 +146,6 @@ impl Network for TestnetV0 { /// The transmission checksum type. type TransmissionChecksum = u128; - /// The network edition. - const EDITION: u16 = 0; /// The genesis block coinbase target. #[cfg(not(feature = "test_targets"))] const GENESIS_COINBASE_TARGET: u64 = (1u64 << 29).saturating_sub(1); @@ -185,16 +183,8 @@ impl Network for TestnetV0 { /// A list of (consensus_version, block_height) pairs indicating when each consensus version takes effect. /// Documentation for what is changed at each version can be found in `ConsensusVersion`. /// Do not read this directly outside of tests, use `N::CONSENSUS_VERSION_HEIGHTS()` instead. - const _CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [ - (ConsensusVersion::V1, 0), - (ConsensusVersion::V2, 2_950_000), - (ConsensusVersion::V3, 4_800_000), - (ConsensusVersion::V4, 6_625_000), - (ConsensusVersion::V5, 6_765_000), - (ConsensusVersion::V6, 7_600_000), - (ConsensusVersion::V7, 8_365_000), - (ConsensusVersion::V8, 9_173_000), - ]; + const _CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = + TESTNET_V0_CONSENSUS_VERSION_HEIGHTS; /// Returns the block height where the the inclusion proof will be updated. #[allow(non_snake_case)] diff --git a/console/program/src/data/plaintext/mod.rs b/console/program/src/data/plaintext/mod.rs index ff1582046f..67441b1a18 100644 --- a/console/program/src/data/plaintext/mod.rs +++ b/console/program/src/data/plaintext/mod.rs @@ -57,6 +57,31 @@ impl From<&Literal> for Plaintext { } } +// A macro that derives implementations of `From` for arrays of a plaintext literals of various sizes. +macro_rules! impl_plaintext_from_array { + ($element:ident, $($size:literal),+) => { + $( + impl From<[$element; $size]> for Plaintext { + fn from(value: [$element; $size]) -> Self { + Self::Array( + value + .into_iter() + .map(|element| Plaintext::from(Literal::$element(element))) + .collect(), + OnceLock::new(), + ) + } + } + )+ + }; +} + +// Implement for `[U8, SIZE]` for sizes 1 through 32. +impl_plaintext_from_array!( + U8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, + 31, 32 +); + #[cfg(test)] mod tests { use super::*; diff --git a/console/program/src/owner/mod.rs b/console/program/src/owner/mod.rs index 09f8e6d121..3aca7ff6e0 100644 --- a/console/program/src/owner/mod.rs +++ b/console/program/src/owner/mod.rs @@ -37,7 +37,7 @@ impl ProgramOwner { // Sign the transaction ID. let signature = private_key.sign(&[deployment_id], rng)?; // Return the program owner. - Ok(Self { signature, address }) + Ok(Self { address, signature }) } /// Initializes a new program owner from an address and signature. diff --git a/console/program/src/request/mod.rs b/console/program/src/request/mod.rs index 26b73d8143..e2ca426bfe 100644 --- a/console/program/src/request/mod.rs +++ b/console/program/src/request/mod.rs @@ -194,9 +194,6 @@ mod test_helpers { let input_external_record = Value::from_str(&record_string).unwrap(); let inputs = vec![input_constant, input_public, input_private, input_record, input_external_record]; - // Construct 'is_root'. - let is_root = false; - // Construct the input types. let input_types = [ ValueType::from_str("amount.constant").unwrap(), @@ -208,11 +205,18 @@ mod test_helpers { // Sample root_tvk. let root_tvk = None; + // Construct 'is_root'. + let is_root = Uniform::rand(rng); + // Sample the program checksum. + let program_checksum = match i % 2 == 0 { + true => Some(Field::rand(rng)), + false => None, + }; // Compute the signed request. let request = - Request::sign(&private_key, program_id, function_name, inputs.into_iter(), &input_types, root_tvk, is_root, rng).unwrap(); - assert!(request.verify(&input_types, is_root)); + Request::sign(&private_key, program_id, function_name, inputs.into_iter(), &input_types, root_tvk, is_root, program_checksum, rng).unwrap(); + assert!(request.verify(&input_types, is_root, program_checksum)); request }) .collect() diff --git a/console/program/src/request/sign.rs b/console/program/src/request/sign.rs index ba6ebeb345..c3b1e30154 100644 --- a/console/program/src/request/sign.rs +++ b/console/program/src/request/sign.rs @@ -17,8 +17,9 @@ use super::*; impl Request { /// Returns the request for a given private key, program ID, function name, inputs, input types, and RNG, where: - /// challenge := HashToScalar(r * G, pk_sig, pr_sig, signer, \[tvk, tcm, function ID, input IDs\]) + /// challenge := HashToScalar(r * G, pk_sig, pr_sig, signer, \[tvk, tcm, function ID, is_root, program checksum?, input IDs\]) /// response := r - challenge * sk_sig + /// The program checksum must be provided if the program has a constructor and should not be provided otherwise. pub fn sign( private_key: &PrivateKey, program_id: ProgramID, @@ -27,6 +28,7 @@ impl Request { input_types: &[ValueType], root_tvk: Option>, is_root: bool, + program_checksum: Option>, rng: &mut R, ) -> Result { // Ensure the number of inputs matches the number of input types. @@ -77,10 +79,14 @@ impl Request { // Compute the function ID. let function_id = compute_function_id(&network_id, &program_id, &function_name)?; - // Construct the hash input as `(r * G, pk_sig, pr_sig, signer, [tvk, tcm, function ID, input IDs])`. + // Construct the hash input as `(r * G, pk_sig, pr_sig, signer, [tvk, tcm, function ID, is_root, program checksum?, input IDs])`. let mut message = Vec::with_capacity(9 + 2 * inputs.len()); message.extend([g_r, pk_sig, pr_sig, *signer].map(|point| point.to_x_coordinate())); message.extend([tvk, tcm, function_id, is_root]); + // Add the program checksum to the hash input if it was provided. + if let Some(program_checksum) = program_checksum { + message.push(program_checksum); + } // Initialize a vector to store the prepared inputs. let mut prepared_inputs = Vec::with_capacity(inputs.len()); @@ -224,7 +230,7 @@ impl Request { } } - // Compute `challenge` as `HashToScalar(r * G, pk_sig, pr_sig, signer, [tvk, tcm, function ID, input IDs])`. + // Compute `challenge` as `HashToScalar(r * G, pk_sig, pr_sig, signer, [tvk, tcm, function ID, is_root, program checksum?, input IDs])`. let challenge = N::hash_to_scalar_psd8(&message)?; // Compute `response` as `r - challenge * sk_sig`. let response = r - challenge * sk_sig; diff --git a/console/program/src/request/verify.rs b/console/program/src/request/verify.rs index 94333852bc..7bd4ae3189 100644 --- a/console/program/src/request/verify.rs +++ b/console/program/src/request/verify.rs @@ -19,8 +19,9 @@ impl Request { /// Returns `true` if the request is valid, and `false` otherwise. /// /// Verifies (challenge == challenge') && (address == address') && (serial_numbers == serial_numbers') where: - /// challenge' := HashToScalar(r * G, pk_sig, pr_sig, signer, \[tvk, tcm, function ID, input IDs\]) - pub fn verify(&self, input_types: &[ValueType], is_root: bool) -> bool { + /// challenge' := HashToScalar(r * G, pk_sig, pr_sig, signer, \[tvk, tcm, function ID, is_root, program checksum?, input IDs\]) + /// The program checksum must be provided if the program has a constructor and should not be provided otherwise. + pub fn verify(&self, input_types: &[ValueType], is_root: bool, program_checksum: Option>) -> bool { // Verify the transition public key, transition view key, and transition commitment are well-formed. { // Compute the transition commitment `tcm` as `Hash(tvk)`. @@ -62,6 +63,10 @@ impl Request { message.push(self.tcm); message.push(function_id); message.push(is_root); + // Add the program checksum to the signature message if it was provided. + if let Some(program_checksum) = program_checksum { + message.push(program_checksum); + } if let Err(error) = self.input_ids.iter().zip_eq(&self.inputs).zip_eq(input_types).enumerate().try_for_each( |(index, ((input_id, input), input_type))| { @@ -226,7 +231,7 @@ mod tests { fn test_sign_and_verify() { let rng = &mut TestRng::default(); - for _ in 0..ITERATIONS { + for i in 0..ITERATIONS { // Sample a random private key and address. let private_key = PrivateKey::::new(rng).unwrap(); let address = Address::try_from(&private_key).unwrap(); @@ -261,6 +266,11 @@ mod tests { let root_tvk = None; // Sample 'is_root'. let is_root = Uniform::rand(rng); + // Sample 'program_checksum'. + let program_checksum = match i % 2 == 0 { + true => Some(Field::rand(rng)), + false => None, + }; // Compute the signed request. let request = Request::sign( @@ -271,10 +281,11 @@ mod tests { &input_types, root_tvk, is_root, + program_checksum, rng, ) .unwrap(); - assert!(request.verify(&input_types, is_root)); + assert!(request.verify(&input_types, is_root, program_checksum)); } } } diff --git a/console/program/src/state_path/configuration/mod.rs b/console/program/src/state_path/configuration/mod.rs index 3f36d9ef29..8d79112d03 100644 --- a/console/program/src/state_path/configuration/mod.rs +++ b/console/program/src/state_path/configuration/mod.rs @@ -57,15 +57,16 @@ pub type TransactionsTree = BHPMerkleTree; /// The Merkle path for a transaction in a block. pub type TransactionsPath = MerklePath; -/// The Merkle tree for the execution. -pub type ExecutionTree = BHPMerkleTree; -/// The Merkle tree for the deployment. -pub type DeploymentTree = BHPMerkleTree; /// The Merkle tree for the transaction. pub type TransactionTree = BHPMerkleTree; /// The Merkle path for a function or transition in the transaction. pub type TransactionPath = MerklePath; +/// The Merkle tree for the execution. +pub type ExecutionTree = BHPMerkleTree; +/// The Merkle tree for the deployment. +pub type DeploymentTree = BHPMerkleTree; + /// The Merkle tree for the transition. pub type TransitionTree = BHPMerkleTree; /// The Merkle path for an input or output ID in the transition. diff --git a/ledger/block/src/transaction/bytes.rs b/ledger/block/src/transaction/bytes.rs index da425aab09..bbc92ac03d 100644 --- a/ledger/block/src/transaction/bytes.rs +++ b/ledger/block/src/transaction/bytes.rs @@ -96,7 +96,8 @@ impl ToBytes for Transaction { 1u8.write_le(&mut writer)?; // Write the transaction. - // We don't write the deployment or execution id, which are recomputed when creating the transaction. + // Note: We purposefully do not write out the deployment or execution ID, + // and instead recompute it when reconstructing the transaction, to ensure there was no malleability. match self { Self::Deploy(id, _, owner, deployment, fee) => { // Write the variant. @@ -147,10 +148,12 @@ mod tests { let rng = &mut TestRng::default(); for expected in [ - crate::transaction::test_helpers::sample_deployment_transaction(0, true, rng), - crate::transaction::test_helpers::sample_deployment_transaction(0, false, rng), - crate::transaction::test_helpers::sample_execution_transaction_with_fee(true, rng), - crate::transaction::test_helpers::sample_execution_transaction_with_fee(false, rng), + crate::transaction::test_helpers::sample_deployment_transaction(1, Uniform::rand(rng), true, rng), + crate::transaction::test_helpers::sample_deployment_transaction(1, Uniform::rand(rng), false, rng), + crate::transaction::test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), true, rng), + crate::transaction::test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), false, rng), + crate::transaction::test_helpers::sample_execution_transaction_with_fee(true, rng, 0), + crate::transaction::test_helpers::sample_execution_transaction_with_fee(false, rng, 0), ] .into_iter() { diff --git a/ledger/block/src/transaction/deployment/bytes.rs b/ledger/block/src/transaction/deployment/bytes.rs index 2cd8eb2b9f..c323dae145 100644 --- a/ledger/block/src/transaction/deployment/bytes.rs +++ b/ledger/block/src/transaction/deployment/bytes.rs @@ -18,12 +18,12 @@ use super::*; impl FromBytes for Deployment { /// Reads the deployment from a buffer. fn read_le(mut reader: R) -> IoResult { - // Read the version. - let version = u8::read_le(&mut reader)?; - // Ensure the version is valid. - if version != 1 { - return Err(error("Invalid deployment version")); - } + // Read the version and ensure the version is valid. + let version = match u8::read_le(&mut reader)? { + 1 => DeploymentVersion::V1, + 2 => DeploymentVersion::V2, + version => return Err(error(format!("Invalid deployment version: {version}"))), + }; // Read the edition. let edition = u16::read_le(&mut reader)?; @@ -45,16 +45,48 @@ impl FromBytes for Deployment { verifying_keys.push((identifier, (verifying_key, certificate))); } + // If the deployment version is 2, read the program checksum and verify it. + let program_checksum = match version { + DeploymentVersion::V1 => None, + DeploymentVersion::V2 => { + // Read the program checksum. + let bytes: [u8; 32] = FromBytes::read_le(&mut reader)?; + let checksum = bytes.map(U8::new); + // Verify the checksum. + if checksum != program.to_checksum() { + return Err(error(format!( + "Invalid checksum in the deployment: expected [{}], got [{}]", + program.to_checksum().iter().join(", "), + checksum.iter().join(", ") + ))); + } + Some(checksum) + } + }; + // If the deployment version is 2, read the program owner. + let program_owner = match version { + DeploymentVersion::V1 => None, + DeploymentVersion::V2 => { + // Read the program owner. + let owner = Address::::read_le(&mut reader)?; + Some(owner) + } + }; + // Return the deployment. - Self::new(edition, program, verifying_keys).map_err(|err| error(format!("{err}"))) + Self::new(edition, program, verifying_keys, program_checksum, program_owner) + .map_err(|err| error(format!("{err}"))) } } impl ToBytes for Deployment { /// Writes the deployment to a buffer. fn write_le(&self, mut writer: W) -> IoResult<()> { + // Determine the version. + // Note: This method checks that either both or neither of the program checksum and program owner are present. + let version = self.version().map_err(error)?; // Write the version. - 1u8.write_le(&mut writer)?; + (version as u8).write_le(&mut writer)?; // Write the edition. self.edition.write_le(&mut writer)?; // Write the program. @@ -70,6 +102,16 @@ impl ToBytes for Deployment { // Write the certificate. certificate.write_le(&mut writer)?; } + // If the deployment version is 2, write the program checksum and program owner. + // Note: The unwraps are safe because `Deployment::version` only returns `V2` if both the checksum and owner is present. + if version == DeploymentVersion::V2 { + // Write the bytes of the checksum. + for byte in &self.program_checksum.unwrap() { + byte.write_le(&mut writer)?; + } + // Write the bytes of the owner. + self.program_owner.unwrap().write_le(&mut writer)?; + } Ok(()) } } @@ -82,12 +124,16 @@ mod tests { fn test_bytes() -> Result<()> { let rng = &mut TestRng::default(); - // Construct a new deployment. - let expected = test_helpers::sample_deployment(0, rng); + // Construct the deployments. + for expected in [ + test_helpers::sample_deployment_v1(Uniform::rand(rng), rng), + test_helpers::sample_deployment_v2(Uniform::rand(rng), rng), + ] { + // Check the byte representation. + let expected_bytes = expected.to_bytes_le()?; + assert_eq!(expected, Deployment::read_le(&expected_bytes[..])?); + } - // Check the byte representation. - let expected_bytes = expected.to_bytes_le()?; - assert_eq!(expected, Deployment::read_le(&expected_bytes[..])?); Ok(()) } } diff --git a/ledger/block/src/transaction/deployment/mod.rs b/ledger/block/src/transaction/deployment/mod.rs index 44fae72bf4..dbb6d2d6d4 100644 --- a/ledger/block/src/transaction/deployment/mod.rs +++ b/ledger/block/src/transaction/deployment/mod.rs @@ -22,13 +22,13 @@ mod string; use crate::Transaction; use console::{ network::prelude::*, - program::{Identifier, ProgramID}, - types::Field, + program::{Address, Identifier, ProgramID}, + types::{Field, U8}, }; use snarkvm_synthesizer_program::Program; use snarkvm_synthesizer_snark::{Certificate, VerifyingKey}; -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone)] pub struct Deployment { /// The edition. edition: u16, @@ -36,17 +36,37 @@ pub struct Deployment { program: Program, /// The mapping of function names to their verifying key and certificate. verifying_keys: Vec<(Identifier, (VerifyingKey, Certificate))>, + /// An optional checksum for the program. + /// This field creates a backwards-compatible implicit versioning mechanism for deployments. + /// Before the migration height where this feature is enabled, the checksum will **not** be allowed. + /// After the migration height where this feature is enabled, the checksum will be required. + program_checksum: Option<[U8; 32]>, + /// An optional owner for the program. + /// This field creates a backwards-compatible implicit versioning mechanism for deployments. + /// Before the migration height where this feature is enabled, the owner will **not** be allowed. + /// After the migration height where this feature is enabled, the owner will be required. + program_owner: Option>, } +impl PartialEq for Deployment { + fn eq(&self, other: &Self) -> bool { + self.edition == other.edition && self.verifying_keys == other.verifying_keys && self.program == other.program + } +} + +impl Eq for Deployment {} + impl Deployment { /// Initializes a new deployment. pub fn new( edition: u16, program: Program, verifying_keys: Vec<(Identifier, (VerifyingKey, Certificate))>, + program_checksum: Option<[U8; 32]>, + program_owner: Option>, ) -> Result { // Construct the deployment. - let deployment = Self { edition, program, verifying_keys }; + let deployment = Self { edition, program, verifying_keys, program_checksum, program_owner }; // Ensure the deployment is ordered. deployment.check_is_ordered()?; // Return the deployment. @@ -57,12 +77,16 @@ impl Deployment { pub fn check_is_ordered(&self) -> Result<()> { let program_id = self.program.id(); - // Ensure the edition is either zero or one. - ensure!( - self.edition == 0 || self.edition == 1, - "Deployed the wrong edition (expected 0 or 1, found '{}').", - self.edition - ); + // Ensure that either the both the program checksum and owner are present, or both are absent. + // The call to `Deployment::version` implicitly performs this check. + self.version()?; + // Validate the deployment based on the program checksum. + if let Some(program_checksum) = self.program_checksum { + ensure!( + program_checksum == self.program.to_checksum(), + "The program checksum in the deployment does not match the computed checksum for '{program_id}'" + ); + } // Ensure the program contains functions. ensure!( !self.program.functions().is_empty(), @@ -112,15 +136,10 @@ impl Deployment { } /// Returns the number of program functions in the deployment. - pub fn len(&self) -> usize { + pub fn num_functions(&self) -> usize { self.program.functions().len() } - /// Returns `true` if the deployment is empty. - pub fn is_empty(&self) -> bool { - self.program.functions().is_empty() - } - /// Returns the edition. pub const fn edition(&self) -> u16 { self.edition @@ -131,6 +150,16 @@ impl Deployment { &self.program } + /// Returns the program checksum, if it was stored. + pub const fn program_checksum(&self) -> Option<[U8; 32]> { + self.program_checksum + } + + /// Returns the program owner, if it was stored. + pub const fn program_owner(&self) -> Option> { + self.program_owner + } + /// Returns the program. pub const fn program_id(&self) -> &ProgramID { self.program.id() @@ -181,6 +210,45 @@ impl Deployment { } } +impl Deployment { + /// Sets the program checksum. + /// Note: This method is intended to be used by the synthesizer **only**, and should not be called by the user. + #[doc(hidden)] + pub fn set_program_checksum_raw(&mut self, program_checksum: Option<[U8; 32]>) { + self.program_checksum = program_checksum; + } + + /// Sets the program owner. + /// Note: This method is intended to be used by the synthesizer **only**, and should not be called by the user. + #[doc(hidden)] + pub fn set_program_owner_raw(&mut self, program_owner: Option>) { + self.program_owner = program_owner; + } + + /// An internal function to return the implicit deployment version. + /// This function implicitly checks that the deployment checksum and owner is well-formed. + #[doc(hidden)] + pub(super) fn version(&self) -> Result { + match (self.program_checksum.is_some(), self.program_owner.is_some()) { + (false, false) => Ok(DeploymentVersion::V1), + (true, true) => Ok(DeploymentVersion::V2), + (true, false) => { + bail!("The program checksum is present, but the program owner is absent.") + } + (false, true) => { + bail!("The program owner is present, but the program checksum is absent.") + } + } + } +} + +// An internal enum to represent the deployment version. +#[derive(Copy, Clone, Eq, PartialEq)] +pub(super) enum DeploymentVersion { + V1 = 1, + V2 = 2, +} + #[cfg(test)] pub mod test_helpers { use super::*; @@ -192,14 +260,14 @@ pub mod test_helpers { type CurrentNetwork = MainnetV0; type CurrentAleo = snarkvm_circuit::network::AleoV0; - pub(crate) fn sample_deployment(edition: u16, rng: &mut TestRng) -> Deployment { + pub(crate) fn sample_deployment_v1(edition: u16, rng: &mut TestRng) -> Deployment { static INSTANCE: OnceLock> = OnceLock::new(); - INSTANCE + let deployment = INSTANCE .get_or_init(|| { // Initialize a new program. let (string, program) = Program::::parse( r" -program testing.aleo; +program testing_three.aleo; mapping store: key as u32.public; @@ -212,22 +280,72 @@ function compute: ) .unwrap(); assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'"); - // Construct the process. let process = Process::load().unwrap(); // Compute the deployment. - let deployment = process.deploy::(&program, rng).unwrap(); - // Create a new deployment with the desired edition. - let deployment = Deployment::::new( - edition, - deployment.program().clone(), - deployment.verifying_keys().clone(), + let mut deployment = process.deploy::(&program, rng).unwrap(); + // Unset the checksum. + deployment.set_program_checksum_raw(None); + // Unset the owner. + deployment.set_program_owner_raw(None); + // Return the deployment. + // Note: This is a testing-only hack to adhere to Rust's dependency cycle rules. + Deployment::from_str(&deployment.to_string()).unwrap() + }) + .clone(); + // Create a new deployment with the desired edition. + // Note the only valid editions for V1 deployments are 0 and 1. + Deployment::::new( + edition % 2, + deployment.program().clone(), + deployment.verifying_keys().clone(), + deployment.program_checksum(), + deployment.program_owner(), + ) + .unwrap() + } + + pub(crate) fn sample_deployment_v2(edition: u16, rng: &mut TestRng) -> Deployment { + static INSTANCE: OnceLock> = OnceLock::new(); + let deployment = INSTANCE + .get_or_init(|| { + // Initialize a new program. + let (string, program) = Program::::parse( + r" +program testing_four.aleo; + +mapping store: + key as u32.public; + value as u32.public; + +function compute: + input r0 as u32.private; + add r0 r0 into r1; + output r1 as u32.public;", ) .unwrap(); + assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'"); + // Construct the process. + let process = Process::load().unwrap(); + // Compute the deployment. + let mut deployment = process.deploy::(&program, rng).unwrap(); + // Set the program checksum. + deployment.set_program_checksum_raw(Some(deployment.program().to_checksum())); + // Set the program owner. + deployment.set_program_owner_raw(Some(Address::rand(rng))); // Return the deployment. // Note: This is a testing-only hack to adhere to Rust's dependency cycle rules. Deployment::from_str(&deployment.to_string()).unwrap() }) - .clone() + .clone(); + // Create a new deployment with the desired edition. + Deployment::::new( + edition, + deployment.program().clone(), + deployment.verifying_keys().clone(), + deployment.program_checksum(), + deployment.program_owner(), + ) + .unwrap() } } diff --git a/ledger/block/src/transaction/deployment/serialize.rs b/ledger/block/src/transaction/deployment/serialize.rs index 1e899816dc..fc661072f8 100644 --- a/ledger/block/src/transaction/deployment/serialize.rs +++ b/ledger/block/src/transaction/deployment/serialize.rs @@ -20,10 +20,21 @@ impl Serialize for Deployment { fn serialize(&self, serializer: S) -> Result { match serializer.is_human_readable() { true => { - let mut deployment = serializer.serialize_struct("Deployment", 3)?; + // Note: `Deployment::version` checks that either both or neither of the program checksum and program owner are present. + let len = match self.version().map_err(ser::Error::custom)? { + DeploymentVersion::V1 => 3, + DeploymentVersion::V2 => 5, + }; + let mut deployment = serializer.serialize_struct("Deployment", len)?; deployment.serialize_field("edition", &self.edition)?; deployment.serialize_field("program", &self.program)?; deployment.serialize_field("verifying_keys", &self.verifying_keys)?; + if let Some(program_checksum) = &self.program_checksum { + deployment.serialize_field("program_checksum", program_checksum)?; + } + if let Some(program_owner) = &self.program_owner { + deployment.serialize_field("program_owner", program_owner)?; + } deployment.end() } false => ToBytesSerializer::serialize_with_size_encoding(self, serializer), @@ -47,6 +58,16 @@ impl<'de, N: Network> Deserialize<'de> for Deployment { DeserializeExt::take_from_value::(&mut deployment, "program")?, // Retrieve the verifying keys. DeserializeExt::take_from_value::(&mut deployment, "verifying_keys")?, + // Retrieve the program checksum, if it exists. + serde_json::from_value( + deployment.get_mut("program_checksum").unwrap_or(&mut serde_json::Value::Null).take(), + ) + .map_err(de::Error::custom)?, + // Retrieve the owner, if it exists. + serde_json::from_value( + deployment.get_mut("program_owner").unwrap_or(&mut serde_json::Value::Null).take(), + ) + .map_err(de::Error::custom)?, ) .map_err(de::Error::custom)?; @@ -65,17 +86,20 @@ mod tests { fn test_serde_json() -> Result<()> { let rng = &mut TestRng::default(); - // Sample the deployment. - let expected = test_helpers::sample_deployment(0, rng); - - // Serialize - let expected_string = &expected.to_string(); - let candidate_string = serde_json::to_string(&expected)?; - assert_eq!(expected, serde_json::from_str(&candidate_string)?); - - // Deserialize - assert_eq!(expected, Deployment::from_str(expected_string)?); - assert_eq!(expected, serde_json::from_str(&candidate_string)?); + // Sample the deployments. + for expected in [ + test_helpers::sample_deployment_v1(Uniform::rand(rng), rng), + test_helpers::sample_deployment_v2(Uniform::rand(rng), rng), + ] { + // Serialize + let expected_string = &expected.to_string(); + let candidate_string = serde_json::to_string(&expected)?; + assert_eq!(expected, serde_json::from_str(&candidate_string)?); + + // Deserialize + assert_eq!(expected, Deployment::from_str(expected_string)?); + assert_eq!(expected, serde_json::from_str(&candidate_string)?); + } Ok(()) } @@ -84,17 +108,20 @@ mod tests { fn test_bincode() -> Result<()> { let rng = &mut TestRng::default(); - // Sample the deployment. - let expected = test_helpers::sample_deployment(0, rng); - - // Serialize - let expected_bytes = expected.to_bytes_le()?; - let expected_bytes_with_size_encoding = bincode::serialize(&expected)?; - assert_eq!(&expected_bytes[..], &expected_bytes_with_size_encoding[8..]); - - // Deserialize - assert_eq!(expected, Deployment::read_le(&expected_bytes[..])?); - assert_eq!(expected, bincode::deserialize(&expected_bytes_with_size_encoding[..])?); + // Sample the deployments + for expected in [ + test_helpers::sample_deployment_v1(Uniform::rand(rng), rng), + test_helpers::sample_deployment_v2(Uniform::rand(rng), rng), + ] { + // Serialize + let expected_bytes = expected.to_bytes_le()?; + let expected_bytes_with_size_encoding = bincode::serialize(&expected)?; + assert_eq!(&expected_bytes[..], &expected_bytes_with_size_encoding[8..]); + + // Deserialize + assert_eq!(expected, Deployment::read_le(&expected_bytes[..])?); + assert_eq!(expected, bincode::deserialize(&expected_bytes_with_size_encoding[..])?); + } Ok(()) } diff --git a/ledger/block/src/transaction/execution/bytes.rs b/ledger/block/src/transaction/execution/bytes.rs index c770a93edd..c6ca9a3308 100644 --- a/ledger/block/src/transaction/execution/bytes.rs +++ b/ledger/block/src/transaction/execution/bytes.rs @@ -82,7 +82,7 @@ mod tests { let rng = &mut TestRng::default(); // Construct a new execution. - let expected = crate::transaction::execution::test_helpers::sample_execution(rng); + let expected = crate::transaction::execution::test_helpers::sample_execution(rng, 0); // Check the byte representation. let expected_bytes = expected.to_bytes_le()?; diff --git a/ledger/block/src/transaction/execution/mod.rs b/ledger/block/src/transaction/execution/mod.rs index 45445a7f5e..2337e35c4c 100644 --- a/ledger/block/src/transaction/execution/mod.rs +++ b/ledger/block/src/transaction/execution/mod.rs @@ -151,12 +151,16 @@ pub mod test_helpers { type CurrentNetwork = console::network::MainnetV0; /// Samples a random execution. - pub(crate) fn sample_execution(rng: &mut TestRng) -> Execution { + pub(crate) fn sample_execution(rng: &mut TestRng, index: usize) -> Execution { // Sample the genesis block. let block = crate::test_helpers::sample_genesis_block(rng); // Retrieve a transaction. - let transaction = block.transactions().iter().next().unwrap().deref().clone(); + let transaction = block.transactions().iter().nth(index).unwrap().deref().clone(); // Retrieve the execution. - if let Transaction::Execute(_, _, execution, _) = transaction { *execution } else { unreachable!() } + if let Transaction::Execute(_, _, execution, _) = transaction { + *execution + } else { + panic!("Index {index} exceeded the number of executions in the genesis block") + } } } diff --git a/ledger/block/src/transaction/execution/serialize.rs b/ledger/block/src/transaction/execution/serialize.rs index 88682b5349..f09a891460 100644 --- a/ledger/block/src/transaction/execution/serialize.rs +++ b/ledger/block/src/transaction/execution/serialize.rs @@ -66,7 +66,7 @@ mod tests { let rng = &mut TestRng::default(); // Sample the execution. - let expected = crate::transaction::execution::test_helpers::sample_execution(rng); + let expected = crate::transaction::execution::test_helpers::sample_execution(rng, 0); // Serialize let expected_string = &expected.to_string(); @@ -85,7 +85,7 @@ mod tests { let rng = &mut TestRng::default(); // Sample the execution. - let expected = crate::transaction::execution::test_helpers::sample_execution(rng); + let expected = crate::transaction::execution::test_helpers::sample_execution(rng, 0); // Serialize let expected_bytes = expected.to_bytes_le()?; diff --git a/ledger/block/src/transaction/merkle.rs b/ledger/block/src/transaction/merkle.rs index 4a5bce4ef7..6d09f92177 100644 --- a/ledger/block/src/transaction/merkle.rs +++ b/ledger/block/src/transaction/merkle.rs @@ -93,17 +93,11 @@ impl Transaction { match self { // Compute the deployment tree. Transaction::Deploy(_, _, _, deployment, fee) => { - let deployment_tree = Self::deployment_tree(deployment)?; - Self::transaction_tree(deployment_tree, deployment.len(), fee) + Self::transaction_tree(Self::deployment_tree(deployment)?, Some(fee)) } // Compute the execution tree. Transaction::Execute(_, _, execution, fee) => { - let execution_tree = Self::execution_tree(execution)?; - if let Some(fee) = fee { - Ok(Transaction::transaction_tree(execution_tree, execution.len(), fee)?) - } else { - Ok(execution_tree) - } + Self::transaction_tree(Self::execution_tree(execution)?, fee.as_ref()) } // Compute the fee tree. Transaction::Fee(_, fee) => Self::fee_tree(fee), @@ -112,28 +106,48 @@ impl Transaction { } impl Transaction { + /// Returns the Merkle tree for the given transaction tree, fee index, and fee. + pub fn transaction_tree( + mut deployment_or_execution_tree: TransactionTree, + fee: Option<&Fee>, + ) -> Result> { + // Retrieve the fee index, defined as the last index in the transaction tree. + let fee_index = deployment_or_execution_tree.number_of_leaves(); + // Ensure the fee index is within the Merkle tree size. + ensure!( + fee_index <= N::MAX_FUNCTIONS, + "The fee index ('{fee_index}') in the transaction tree must be at most {}", + N::MAX_FUNCTIONS + ); + // Ensure the fee index is within the Merkle tree size. + ensure!( + fee_index < Self::MAX_TRANSITIONS, + "The fee index ('{fee_index}') in the transaction tree must be less than {}", + Self::MAX_TRANSITIONS + ); + + // If a fee is provided, append the fee leaf to the transaction tree. + if let Some(fee) = fee { + // Construct the transaction leaf. + let leaf = TransactionLeaf::new_fee(u16::try_from(fee_index)?, **fee.transition_id()).to_bits_le(); + // Append the fee leaf to the transaction tree. + deployment_or_execution_tree.append(&[leaf])?; + } + // Return the transaction tree. + Ok(deployment_or_execution_tree) + } + /// Returns the Merkle tree for the given deployment. pub fn deployment_tree(deployment: &Deployment) -> Result> { - // Ensure the number of leaves is within the Merkle tree size. - Self::check_deployment_size(deployment)?; - // Retrieve the program. - let program = deployment.program(); - // Prepare the leaves. - let leaves = program - .functions() - .values() - .enumerate() - .map(|(index, function)| { - // Construct the transaction leaf. - Ok(TransactionLeaf::new_deployment( - u16::try_from(index)?, - N::hash_bhp1024(&to_bits_le![program.id(), function.to_bytes_le()?])?, - ) - .to_bits_le()) - }) - .collect::>>()?; - // Compute the deployment tree. - N::merkle_tree_bhp::(&leaves) + // Use the V1 or V2 deployment tree based on implicit deployment version. + // Note: `ConsensusVersion::V9` requires the program checksum and program owner to be present, while prior versions require it to be absent. + // `Deployment::version` checks that this is the case. + // Note: After `ConsensusVersion::V9`, the program checksum and owner are used in the header of the hash instead of the program ID. + match deployment.version() { + Ok(DeploymentVersion::V1) => Self::deployment_tree_v1(deployment), + Ok(DeploymentVersion::V2) => Self::deployment_tree_v2(deployment), + Err(e) => bail!("Malformed deployment - {e}"), + } } /// Returns the Merkle tree for the given execution. @@ -150,29 +164,12 @@ impl Transaction { // Ensure the number of leaves is within the Merkle tree size. Self::check_execution_size(num_transitions)?; // Prepare the leaves. - let leaves = transitions - .enumerate() - .map(|(index, transition)| { - // Construct the transaction leaf. - Ok::<_, Error>(TransactionLeaf::new_execution(u16::try_from(index)?, **transition.id()).to_bits_le()) - }) - .collect::, _>>()?; + let leaves = transitions.enumerate().map(|(index, transition)| { + // Construct the transaction leaf. + Ok::<_, Error>(TransactionLeaf::new_execution(u16::try_from(index)?, **transition.id()).to_bits_le()) + }); // Compute the execution tree. - N::merkle_tree_bhp::(&leaves) - } - - /// Returns the Merkle tree for the given 1. transaction or deployment tree and 2. fee. - pub fn transaction_tree( - mut deployment_or_execution_tree: TransactionTree, - fee_index: usize, - fee: &Fee, - ) -> Result> { - // Construct the transaction leaf. - let leaf = TransactionLeaf::new_fee(u16::try_from(fee_index)?, **fee.transition_id()).to_bits_le(); - // Compute the updated transaction tree. - deployment_or_execution_tree.append(&[leaf])?; - - Ok(deployment_or_execution_tree) + N::merkle_tree_bhp::(&leaves.collect::, _>>()?) } /// Returns the Merkle tree for the given fee. @@ -191,20 +188,28 @@ impl Transaction { let functions = program.functions(); // Retrieve the verifying keys. let verifying_keys = deployment.verifying_keys(); + // Retrieve the number of functions. + let num_functions = functions.len(); // Ensure the number of functions and verifying keys match. ensure!( - functions.len() == verifying_keys.len(), - "Number of functions ('{}') and verifying keys ('{}') do not match", - functions.len(), + num_functions == verifying_keys.len(), + "Number of functions ('{num_functions}') and verifying keys ('{}') do not match", verifying_keys.len() ); + // Ensure there are functions. + ensure!(num_functions != 0, "Deployment must contain at least one function"); // Ensure the number of functions is within the allowed range. ensure!( - functions.len() < Self::MAX_TRANSITIONS, // Note: Observe we hold back 1 for the fee. - "Deployment must contain less than {} functions, found {}", + num_functions <= N::MAX_FUNCTIONS, + "Deployment must contain at most {} functions, found {num_functions}", + N::MAX_FUNCTIONS, + ); + // Ensure the number of functions is within the allowed range. + ensure!( + num_functions < Self::MAX_TRANSITIONS, // Note: Observe we hold back 1 for the fee. + "Deployment must contain less than {} functions, found {num_functions}", Self::MAX_TRANSITIONS, - functions.len() ); Ok(()) } @@ -216,9 +221,72 @@ impl Transaction { // Ensure the number of functions is within the allowed range. ensure!( num_transitions < Self::MAX_TRANSITIONS, // Note: Observe we hold back 1 for the fee. - "Execution must contain less than {num_transitions} transitions, found {}", + "Execution must contain less than {} transitions, found {num_transitions}", Self::MAX_TRANSITIONS, ); Ok(()) } } + +impl Transaction { + /// Returns the V1 deployment tree. + pub fn deployment_tree_v1(deployment: &Deployment) -> Result> { + // Ensure the number of leaves is within the Merkle tree size. + Self::check_deployment_size(deployment)?; + // Prepare the header for the hash. + let header = deployment.program().id().to_bits_le(); + // Prepare the leaves. + let leaves = deployment.program().functions().values().enumerate().map(|(index, function)| { + // Construct the transaction leaf. + Ok(TransactionLeaf::new_deployment( + u16::try_from(index)?, + N::hash_bhp1024(&to_bits_le![header, function.to_bytes_le()?])?, + ) + .to_bits_le()) + }); + // Compute the deployment tree. + N::merkle_tree_bhp::(&leaves.collect::>>()?) + } + + /// Returns the V2 deployment tree. + pub fn deployment_tree_v2(deployment: &Deployment) -> Result> { + // Ensure the number of leaves is within the Merkle tree size. + Self::check_deployment_size(deployment)?; + // Compute a hash of the deployment bytes. + let deployment_hash = N::hash_sha3_256(&to_bits_le!(deployment.to_bytes_le()?))?; + // Prepare the header for the hash. + let header = to_bits_le![deployment.version()? as u8, deployment_hash]; + // Prepare the leaves. + let leaves = deployment.program().functions().values().enumerate().map(|(index, function)| { + // Construct the transaction leaf. + Ok(TransactionLeaf::new_deployment( + u16::try_from(index)?, + N::hash_bhp1024(&to_bits_le![header, function.to_bytes_le()?])?, + ) + .to_bits_le()) + }); + // Compute the deployment tree. + N::merkle_tree_bhp::(&leaves.collect::>>()?) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + type CurrentNetwork = console::network::MainnetV0; + + #[test] + fn test_transaction_depth_is_correct() { + // We ensure 2^TRANSACTION_DEPTH == MAX_FUNCTIONS + 1. + // The "1 extra" is for the fee transition. + assert_eq!( + 2u32.checked_pow(TRANSACTION_DEPTH as u32).unwrap() as usize, + Transaction::::MAX_TRANSITIONS + ); + assert_eq!( + CurrentNetwork::MAX_FUNCTIONS.checked_add(1).unwrap(), + Transaction::::MAX_TRANSITIONS + ); + } +} diff --git a/ledger/block/src/transaction/mod.rs b/ledger/block/src/transaction/mod.rs index 7cb3d99f35..10072196c1 100644 --- a/ledger/block/src/transaction/mod.rs +++ b/ledger/block/src/transaction/mod.rs @@ -67,9 +67,18 @@ impl Transaction { // Compute the deployment ID. let deployment_id = *deployment_tree.root(); // Compute the transaction ID - let transaction_id = *Self::transaction_tree(deployment_tree, deployment.len(), &fee)?.root(); + let transaction_id = *Self::transaction_tree(deployment_tree, Some(&fee))?.root(); // Ensure the owner signed the correct transaction ID. ensure!(owner.verify(deployment_id), "Attempted to create a deployment transaction with an invalid owner"); + // Ensure the owner matches the program owner in the deployment, if it exists. + if let Some(program_owner) = deployment.program_owner() { + ensure!( + owner.address() == program_owner, + "Attempted to create a deployment transaction with a provided owner '{}' and deployment owner '{}' that do not match", + owner.address(), + program_owner + ) + } // Construct the deployment transaction. Ok(Self::Deploy(transaction_id.into(), deployment_id, owner, Box::new(deployment), fee)) } @@ -82,14 +91,8 @@ impl Transaction { let execution_tree = Self::execution_tree(&execution)?; // Compute the execution ID. let execution_id = *execution_tree.root(); - // Compute the transaction ID - let transaction_id = match &fee { - Some(fee) => { - // Compute the root of the transaction tree. - *Self::transaction_tree(execution_tree, execution.len(), fee)?.root() - } - None => execution_id, - }; + // Compute the transaction ID. + let transaction_id = *Self::transaction_tree(execution_tree, fee.as_ref())?.root(); // Construct the execution transaction. Ok(Self::Execute(transaction_id.into(), execution_id, Box::new(execution), fee)) } @@ -446,12 +449,13 @@ impl Transaction { #[cfg(test)] pub mod test_helpers { use super::*; - use console::{account::PrivateKey, network::MainnetV0, program::ProgramOwner}; + use console::{account::PrivateKey, network::MainnetV0, program::ProgramOwner, types::Address}; type CurrentNetwork = MainnetV0; /// Samples a random deployment transaction with a private or public fee. pub fn sample_deployment_transaction( + version: u8, edition: u16, is_fee_private: bool, rng: &mut TestRng, @@ -459,7 +463,19 @@ pub mod test_helpers { // Sample a private key. let private_key = PrivateKey::new(rng).unwrap(); // Sample a deployment. - let deployment = crate::transaction::deployment::test_helpers::sample_deployment(edition, rng); + let deployment = match version { + 1 => crate::transaction::deployment::test_helpers::sample_deployment_v1(edition, rng), + 2 => { + let mut deployment = crate::transaction::deployment::test_helpers::sample_deployment_v2(edition, rng); + // Set the program checksum. + deployment.set_program_checksum_raw(Some(deployment.program().to_checksum())); + // Set the program owner to the address of the private key. + deployment.set_program_owner_raw(Some(Address::try_from(&private_key).unwrap())); + // Return the deployment. + deployment + } + _ => panic!("Invalid deployment version."), + }; // Compute the deployment ID. let deployment_id = deployment.to_deployment_id().unwrap(); @@ -480,9 +496,10 @@ pub mod test_helpers { pub fn sample_execution_transaction_with_fee( is_fee_private: bool, rng: &mut TestRng, + index: usize, ) -> Transaction { // Sample an execution. - let execution = crate::transaction::execution::test_helpers::sample_execution(rng); + let execution = crate::transaction::execution::test_helpers::sample_execution(rng, index); // Compute the execution ID. let execution_id = execution.to_execution_id().unwrap(); @@ -523,10 +540,16 @@ mod tests { // Transaction IDs are created using `transaction_tree`. for expected in [ - crate::transaction::test_helpers::sample_deployment_transaction(0, true, rng), - crate::transaction::test_helpers::sample_deployment_transaction(0, false, rng), - crate::transaction::test_helpers::sample_execution_transaction_with_fee(true, rng), - crate::transaction::test_helpers::sample_execution_transaction_with_fee(false, rng), + crate::transaction::test_helpers::sample_deployment_transaction(1, Uniform::rand(rng), true, rng), + crate::transaction::test_helpers::sample_deployment_transaction(1, Uniform::rand(rng), true, rng), + crate::transaction::test_helpers::sample_deployment_transaction(1, Uniform::rand(rng), false, rng), + crate::transaction::test_helpers::sample_deployment_transaction(1, Uniform::rand(rng), false, rng), + crate::transaction::test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), true, rng), + crate::transaction::test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), true, rng), + crate::transaction::test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), false, rng), + crate::transaction::test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), false, rng), + crate::transaction::test_helpers::sample_execution_transaction_with_fee(true, rng, 0), + crate::transaction::test_helpers::sample_execution_transaction_with_fee(false, rng, 0), ] .into_iter() { diff --git a/ledger/block/src/transaction/serialize.rs b/ledger/block/src/transaction/serialize.rs index 234bb0905e..00fcb7fbcc 100644 --- a/ledger/block/src/transaction/serialize.rs +++ b/ledger/block/src/transaction/serialize.rs @@ -18,8 +18,9 @@ use super::*; impl Serialize for Transaction { /// Serializes the transaction to a JSON-string or buffer. fn serialize(&self, serializer: S) -> Result { + // Note: We purposefully do not write out the deployment or execution ID, + // and instead recompute it when reconstructing the transaction, to ensure there was no malleability. match serializer.is_human_readable() { - // We don't write the deployment or execution id, which are recomputed when creating the Transaction. true => match self { Self::Deploy(id, _, owner, deployment, fee) => { let mut transaction = serializer.serialize_struct("Transaction", 5)?; @@ -119,10 +120,12 @@ mod tests { let rng = &mut TestRng::default(); for expected in [ - crate::transaction::test_helpers::sample_deployment_transaction(0, true, rng), - crate::transaction::test_helpers::sample_deployment_transaction(0, false, rng), - crate::transaction::test_helpers::sample_execution_transaction_with_fee(true, rng), - crate::transaction::test_helpers::sample_execution_transaction_with_fee(false, rng), + crate::transaction::test_helpers::sample_deployment_transaction(1, Uniform::rand(rng), true, rng), + crate::transaction::test_helpers::sample_deployment_transaction(1, Uniform::rand(rng), false, rng), + crate::transaction::test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), true, rng), + crate::transaction::test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), false, rng), + crate::transaction::test_helpers::sample_execution_transaction_with_fee(true, rng, 0), + crate::transaction::test_helpers::sample_execution_transaction_with_fee(false, rng, 0), ] .into_iter() { @@ -142,10 +145,12 @@ mod tests { let rng = &mut TestRng::default(); for expected in [ - crate::transaction::test_helpers::sample_deployment_transaction(0, true, rng), - crate::transaction::test_helpers::sample_deployment_transaction(0, false, rng), - crate::transaction::test_helpers::sample_execution_transaction_with_fee(true, rng), - crate::transaction::test_helpers::sample_execution_transaction_with_fee(false, rng), + crate::transaction::test_helpers::sample_deployment_transaction(1, Uniform::rand(rng), true, rng), + crate::transaction::test_helpers::sample_deployment_transaction(1, Uniform::rand(rng), false, rng), + crate::transaction::test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), true, rng), + crate::transaction::test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), false, rng), + crate::transaction::test_helpers::sample_execution_transaction_with_fee(true, rng, 0), + crate::transaction::test_helpers::sample_execution_transaction_with_fee(false, rng, 0), ] .into_iter() { diff --git a/ledger/block/src/transactions/confirmed/mod.rs b/ledger/block/src/transactions/confirmed/mod.rs index b7de64ec6b..d88a63fef8 100644 --- a/ledger/block/src/transactions/confirmed/mod.rs +++ b/ledger/block/src/transactions/confirmed/mod.rs @@ -27,12 +27,16 @@ pub type NumFinalizeSize = u16; #[derive(Clone, PartialEq, Eq)] pub enum ConfirmedTransaction { /// The accepted deploy transaction is composed of `(index, deploy_transaction, finalize_operations)`. + /// The finalize operations may contain operations from the executing the constructor and fee transition. AcceptedDeploy(u32, Transaction, Vec>), /// The accepted execute transaction is composed of `(index, execute_transaction, finalize_operations)`. + /// The finalize operations can contain operations from the executing the finalize scope and fee transition. AcceptedExecute(u32, Transaction, Vec>), /// The rejected deploy transaction is composed of `(index, fee_transaction, rejected_deployment, finalize_operations)`. + /// The finalize operations can contain operations from the fee transition. RejectedDeploy(u32, Transaction, Rejected, Vec>), /// The rejected execute transaction is composed of `(index, fee_transaction, rejected_execution, finalize_operations)`. + /// The finalize operations can contain operations from the fee transition. RejectedExecute(u32, Transaction, Rejected, Vec>), } @@ -51,11 +55,13 @@ impl ConfirmedTransaction { } }; - // Count the number of `InitializeMapping` and `UpdateKeyValue` finalize operations. - let (num_initialize_mappings, num_update_key_values) = - finalize_operations.iter().try_fold((0, 0), |(init, update), operation| match operation { - FinalizeOperation::InitializeMapping(..) => Ok((init + 1, update)), - FinalizeOperation::UpdateKeyValue(..) => Ok((init, update + 1)), + // Count the number of `InitializeMapping` and `*KeyValue` finalize operations. + let (num_initialize_mappings, num_key_values) = + finalize_operations.iter().try_fold((0, 0), |(init, key_value), operation| match operation { + FinalizeOperation::InitializeMapping(..) => Ok((init + 1, key_value)), + FinalizeOperation::InsertKeyValue(..) // At the time of writing, `InsertKeyValue` is only used in tests. However, it is added for completeness, as it is a valid operation. + | FinalizeOperation::RemoveKeyValue(..) + | FinalizeOperation::UpdateKeyValue(..) => Ok((init, key_value + 1)), op => { bail!("Transaction '{}' (deploy) contains an invalid finalize operation ({op})", transaction.id()) } @@ -63,31 +69,39 @@ impl ConfirmedTransaction { // Perform safety checks on the finalize operations. { - // Ensure the number of finalize operations matches the number of 'InitializeMapping' and 'UpdateKeyValue' finalize operations. - if num_initialize_mappings + num_update_key_values != finalize_operations.len() { + // Ensure the number of finalize operations matches the number of 'InitializeMapping' and '*KeyValue' finalize operations. + if num_initialize_mappings + num_key_values != finalize_operations.len() { bail!( "Transaction '{}' (deploy) must contain '{}' operations", transaction.id(), finalize_operations.len() ); } - // Ensure the number of 'InitializeMapping' finalize operations either matches the number of program mappings or is zero. - // The first case happens when a program is initially deployed. The second case happens when a program is re-deployed. - if num_initialize_mappings != program.mappings().len() && num_initialize_mappings != 0 { - bail!( - "Transaction '{}' (deploy) must contain either '{}' or zero 'InitializeMapping' operations (found '{num_initialize_mappings}')", - transaction.id(), - program.mappings().len(), - ) - } - // Ensure the number of finalize operations matches the number of 'UpdateKeyValue' finalize operations. - if num_update_key_values != fee.num_finalize_operations() { - bail!( - "Transaction '{}' (deploy) must contain {} 'UpdateKeyValue' operations (found '{num_update_key_values}')", - transaction.id(), - fee.num_finalize_operations() - ); - } + // Ensure the number of program mappings upper bounds the number of 'InitializeMapping' finalize operations. + // The upper bound is due to the fact that some mappings may have been initialized in earlier deployments or upgrades. + ensure!( + num_initialize_mappings <= program.mappings().len(), + "Transaction '{}' (deploy) must contain at most '{}' 'InitializeMapping' operations (found '{num_initialize_mappings}')", + transaction.id(), + program.mappings().len(), + ); + // Ensure the number of fee finalize operations lower bounds the number of '*KeyValue' finalize operations. + // The lower bound is due to the fact that constructors can issue '*KeyValue' operations as part of the deployment. + ensure!( + fee.num_finalize_operations() <= num_key_values, + "Transaction '{}' (deploy) must contain at least {} '*KeyValue' operations (found '{num_key_values}')", + transaction.id(), + fee.num_finalize_operations() + ); + // Ensure the number of fee finalize operations and the number of "write" operations in the constructor upper bounds the number of '*KeyValue' finalize operations. + // This is an upper bound because a constructor may contain `branch.*` commands so that a subset of writes are executed. + let num_constructor_writes = usize::from(program.constructor().map(|c| c.num_writes()).unwrap_or_default()); + ensure!( + fee.num_finalize_operations().saturating_add(num_constructor_writes) >= num_key_values, + "Transaction '{}' (deploy) must contain at most {} '*KeyValue' operations (found '{num_key_values}')", + transaction.id(), + fee.num_finalize_operations().saturating_add(num_constructor_writes) + ); } // Return the accepted deploy transaction. @@ -356,11 +370,13 @@ pub mod test_helpers { /// Samples an accepted deploy transaction at the given index. pub(crate) fn sample_accepted_deploy( index: u32, + version: u8, + edition: u16, is_fee_private: bool, rng: &mut TestRng, ) -> ConfirmedTransaction { // Sample a deploy transaction. - let tx = crate::transaction::test_helpers::sample_deployment_transaction(0, is_fee_private, rng); + let tx = crate::transaction::test_helpers::sample_deployment_transaction(version, edition, is_fee_private, rng); // Construct the finalize operations based on if the fee is public or private. let finalize_operations = match is_fee_private { @@ -382,7 +398,7 @@ pub mod test_helpers { rng: &mut TestRng, ) -> ConfirmedTransaction { // Sample an execute transaction. - let tx = crate::transaction::test_helpers::sample_execution_transaction_with_fee(is_fee_private, rng); + let tx = crate::transaction::test_helpers::sample_execution_transaction_with_fee(is_fee_private, rng, 0); // Return the confirmed transaction. ConfirmedTransaction::accepted_execute(index, tx, vec![]).unwrap() } @@ -390,6 +406,7 @@ pub mod test_helpers { /// Samples a rejected deploy transaction at the given index. pub(crate) fn sample_rejected_deploy( index: u32, + version: u8, edition: u16, is_fee_private: bool, rng: &mut TestRng, @@ -401,7 +418,7 @@ pub mod test_helpers { }; // Extract the rejected deployment. - let rejected = crate::rejected::test_helpers::sample_rejected_deployment(edition, is_fee_private, rng); + let rejected = crate::rejected::test_helpers::sample_rejected_deployment(version, edition, is_fee_private, rng); // Return the confirmed transaction. ConfirmedTransaction::rejected_deploy(index, fee_transaction, rejected, vec![]).unwrap() @@ -431,20 +448,28 @@ pub mod test_helpers { let rng = &mut TestRng::default(); vec![ - sample_accepted_deploy(0, true, rng), - sample_accepted_deploy(0, false, rng), + sample_accepted_deploy(0, 1, Uniform::rand(rng), true, rng), + sample_accepted_deploy(0, 1, Uniform::rand(rng), true, rng), + sample_accepted_deploy(0, 2, Uniform::rand(rng), true, rng), + sample_accepted_deploy(0, 2, Uniform::rand(rng), true, rng), sample_accepted_execute(1, true, rng), sample_accepted_execute(1, false, rng), - sample_rejected_deploy(2, 0, true, rng), - sample_rejected_deploy(2, 0, false, rng), + sample_rejected_deploy(2, 1, Uniform::rand(rng), true, rng), + sample_rejected_deploy(2, 1, Uniform::rand(rng), true, rng), + sample_rejected_deploy(2, 2, Uniform::rand(rng), true, rng), + sample_rejected_deploy(2, 2, Uniform::rand(rng), true, rng), sample_rejected_execute(3, true, rng), sample_rejected_execute(3, false, rng), - sample_accepted_deploy(Uniform::rand(rng), true, rng), - sample_accepted_deploy(Uniform::rand(rng), false, rng), sample_accepted_execute(Uniform::rand(rng), true, rng), sample_accepted_execute(Uniform::rand(rng), false, rng), - sample_rejected_deploy(Uniform::rand(rng), 0, true, rng), - sample_rejected_deploy(Uniform::rand(rng), 0, false, rng), + sample_rejected_deploy(Uniform::rand(rng), 1, Uniform::rand(rng), true, rng), + sample_rejected_deploy(Uniform::rand(rng), 1, Uniform::rand(rng), false, rng), + sample_rejected_deploy(Uniform::rand(rng), 1, Uniform::rand(rng), true, rng), + sample_rejected_deploy(Uniform::rand(rng), 1, Uniform::rand(rng), false, rng), + sample_rejected_deploy(Uniform::rand(rng), 2, Uniform::rand(rng), true, rng), + sample_rejected_deploy(Uniform::rand(rng), 2, Uniform::rand(rng), false, rng), + sample_rejected_deploy(Uniform::rand(rng), 2, Uniform::rand(rng), true, rng), + sample_rejected_deploy(Uniform::rand(rng), 2, Uniform::rand(rng), false, rng), sample_rejected_execute(Uniform::rand(rng), true, rng), sample_rejected_execute(Uniform::rand(rng), false, rng), ] @@ -463,7 +488,7 @@ mod test { let rng = &mut TestRng::default(); let index = Uniform::rand(rng); - let tx = crate::transaction::test_helpers::sample_execution_transaction_with_fee(true, rng); + let tx = crate::transaction::test_helpers::sample_execution_transaction_with_fee(true, rng, 0); // Create an `AcceptedExecution` with valid `FinalizeOperation`s. let finalize_operations = vec![ @@ -503,9 +528,17 @@ mod test { }; // Ensure that the unconfirmed transaction ID of an accepted deployment is equivalent to its confirmed transaction ID. - let accepted_deploy = test_helpers::sample_accepted_deploy(Uniform::rand(rng), true, rng); + let accepted_deploy = + test_helpers::sample_accepted_deploy(Uniform::rand(rng), 1, Uniform::rand(rng), true, rng); + check_contains_unconfirmed_transaction_id(accepted_deploy); + let accepted_deploy = + test_helpers::sample_accepted_deploy(Uniform::rand(rng), 1, Uniform::rand(rng), false, rng); + check_contains_unconfirmed_transaction_id(accepted_deploy); + let accepted_deploy = + test_helpers::sample_accepted_deploy(Uniform::rand(rng), 2, Uniform::rand(rng), true, rng); check_contains_unconfirmed_transaction_id(accepted_deploy); - let accepted_deploy = test_helpers::sample_accepted_deploy(Uniform::rand(rng), false, rng); + let accepted_deploy = + test_helpers::sample_accepted_deploy(Uniform::rand(rng), 2, Uniform::rand(rng), false, rng); check_contains_unconfirmed_transaction_id(accepted_deploy); // Ensure that the unconfirmed transaction ID of an accepted execute is equivalent to its confirmed transaction ID. @@ -515,9 +548,17 @@ mod test { check_contains_unconfirmed_transaction_id(accepted_execution); // Ensure that the unconfirmed transaction ID of a rejected deployment is not equivalent to its confirmed transaction ID. - let rejected_deploy = test_helpers::sample_rejected_deploy(Uniform::rand(rng), 0, true, rng); + let rejected_deploy = + test_helpers::sample_rejected_deploy(Uniform::rand(rng), 1, Uniform::rand(rng), true, rng); check_contains_unconfirmed_transaction_id(rejected_deploy); - let rejected_deploy = test_helpers::sample_rejected_deploy(Uniform::rand(rng), 0, false, rng); + let rejected_deploy = + test_helpers::sample_rejected_deploy(Uniform::rand(rng), 1, Uniform::rand(rng), false, rng); + check_contains_unconfirmed_transaction_id(rejected_deploy); + let rejected_deploy = + test_helpers::sample_rejected_deploy(Uniform::rand(rng), 2, Uniform::rand(rng), true, rng); + check_contains_unconfirmed_transaction_id(rejected_deploy); + let rejected_deploy = + test_helpers::sample_rejected_deploy(Uniform::rand(rng), 2, Uniform::rand(rng), false, rng); check_contains_unconfirmed_transaction_id(rejected_deploy); // Ensure that the unconfirmed transaction ID of a rejected execute is not equivalent to its confirmed transaction ID. @@ -532,9 +573,17 @@ mod test { let rng = &mut TestRng::default(); // Ensure that the unconfirmed transaction ID of an accepted deployment is equivalent to its confirmed transaction ID. - let accepted_deploy = test_helpers::sample_accepted_deploy(Uniform::rand(rng), true, rng); + let accepted_deploy = + test_helpers::sample_accepted_deploy(Uniform::rand(rng), 1, Uniform::rand(rng), true, rng); + assert_eq!(accepted_deploy.to_unconfirmed_transaction_id().unwrap(), accepted_deploy.id()); + let accepted_deploy = + test_helpers::sample_accepted_deploy(Uniform::rand(rng), 1, Uniform::rand(rng), false, rng); assert_eq!(accepted_deploy.to_unconfirmed_transaction_id().unwrap(), accepted_deploy.id()); - let accepted_deploy = test_helpers::sample_accepted_deploy(Uniform::rand(rng), false, rng); + let accepted_deploy = + test_helpers::sample_accepted_deploy(Uniform::rand(rng), 2, Uniform::rand(rng), true, rng); + assert_eq!(accepted_deploy.to_unconfirmed_transaction_id().unwrap(), accepted_deploy.id()); + let accepted_deploy = + test_helpers::sample_accepted_deploy(Uniform::rand(rng), 2, Uniform::rand(rng), false, rng); assert_eq!(accepted_deploy.to_unconfirmed_transaction_id().unwrap(), accepted_deploy.id()); // Ensure that the unconfirmed transaction ID of an accepted execute is equivalent to its confirmed transaction ID. @@ -544,9 +593,17 @@ mod test { assert_eq!(accepted_execution.to_unconfirmed_transaction_id().unwrap(), accepted_execution.id()); // Ensure that the unconfirmed transaction ID of a rejected deployment is not equivalent to its confirmed transaction ID. - let rejected_deploy = test_helpers::sample_rejected_deploy(Uniform::rand(rng), 0, true, rng); + let rejected_deploy = + test_helpers::sample_rejected_deploy(Uniform::rand(rng), 1, Uniform::rand(rng), true, rng); + assert_ne!(rejected_deploy.to_unconfirmed_transaction_id().unwrap(), rejected_deploy.id()); + let rejected_deploy = + test_helpers::sample_rejected_deploy(Uniform::rand(rng), 1, Uniform::rand(rng), false, rng); assert_ne!(rejected_deploy.to_unconfirmed_transaction_id().unwrap(), rejected_deploy.id()); - let rejected_deploy = test_helpers::sample_rejected_deploy(Uniform::rand(rng), 0, false, rng); + let rejected_deploy = + test_helpers::sample_rejected_deploy(Uniform::rand(rng), 2, Uniform::rand(rng), true, rng); + assert_ne!(rejected_deploy.to_unconfirmed_transaction_id().unwrap(), rejected_deploy.id()); + let rejected_deploy = + test_helpers::sample_rejected_deploy(Uniform::rand(rng), 2, Uniform::rand(rng), false, rng); assert_ne!(rejected_deploy.to_unconfirmed_transaction_id().unwrap(), rejected_deploy.id()); // Ensure that the unconfirmed transaction ID of a rejected execute is not equivalent to its confirmed transaction ID. @@ -561,9 +618,17 @@ mod test { let rng = &mut TestRng::default(); // Ensure that the unconfirmed transaction of an accepted deployment is equivalent to its confirmed transaction. - let accepted_deploy = test_helpers::sample_accepted_deploy(Uniform::rand(rng), true, rng); + let accepted_deploy = + test_helpers::sample_accepted_deploy(Uniform::rand(rng), 1, Uniform::rand(rng), true, rng); + assert_eq!(&accepted_deploy.to_unconfirmed_transaction().unwrap(), accepted_deploy.transaction()); + let accepted_deploy = + test_helpers::sample_accepted_deploy(Uniform::rand(rng), 1, Uniform::rand(rng), false, rng); assert_eq!(&accepted_deploy.to_unconfirmed_transaction().unwrap(), accepted_deploy.transaction()); - let accepted_deploy = test_helpers::sample_accepted_deploy(Uniform::rand(rng), false, rng); + let accepted_deploy = + test_helpers::sample_accepted_deploy(Uniform::rand(rng), 2, Uniform::rand(rng), true, rng); + assert_eq!(&accepted_deploy.to_unconfirmed_transaction().unwrap(), accepted_deploy.transaction()); + let accepted_deploy = + test_helpers::sample_accepted_deploy(Uniform::rand(rng), 2, Uniform::rand(rng), false, rng); assert_eq!(&accepted_deploy.to_unconfirmed_transaction().unwrap(), accepted_deploy.transaction()); // Ensure that the unconfirmed transaction of an accepted execute is equivalent to its confirmed transaction. @@ -573,7 +638,19 @@ mod test { assert_eq!(&accepted_execution.to_unconfirmed_transaction().unwrap(), accepted_execution.transaction()); // Ensure that the unconfirmed transaction of a rejected deployment is not equivalent to its confirmed transaction. - let deployment_transaction = crate::transaction::test_helpers::sample_deployment_transaction(0, true, rng); + let deployment_transaction = + crate::transaction::test_helpers::sample_deployment_transaction(1, Uniform::rand(rng), true, rng); + let rejected = Rejected::new_deployment( + *deployment_transaction.owner().unwrap(), + deployment_transaction.deployment().unwrap().clone(), + ); + let fee = Transaction::from_fee(deployment_transaction.fee_transition().unwrap()).unwrap(); + let rejected_deploy = ConfirmedTransaction::rejected_deploy(Uniform::rand(rng), fee, rejected, vec![]).unwrap(); + assert_eq!(rejected_deploy.to_unconfirmed_transaction_id().unwrap(), deployment_transaction.id()); + assert_eq!(rejected_deploy.to_unconfirmed_transaction().unwrap(), deployment_transaction); + + let deployment_transaction = + crate::transaction::test_helpers::sample_deployment_transaction(1, Uniform::rand(rng), false, rng); let rejected = Rejected::new_deployment( *deployment_transaction.owner().unwrap(), deployment_transaction.deployment().unwrap().clone(), @@ -582,7 +659,20 @@ mod test { let rejected_deploy = ConfirmedTransaction::rejected_deploy(Uniform::rand(rng), fee, rejected, vec![]).unwrap(); assert_eq!(rejected_deploy.to_unconfirmed_transaction_id().unwrap(), deployment_transaction.id()); assert_eq!(rejected_deploy.to_unconfirmed_transaction().unwrap(), deployment_transaction); - let deployment_transaction = crate::transaction::test_helpers::sample_deployment_transaction(0, false, rng); + + let deployment_transaction = + crate::transaction::test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), true, rng); + let rejected = Rejected::new_deployment( + *deployment_transaction.owner().unwrap(), + deployment_transaction.deployment().unwrap().clone(), + ); + let fee = Transaction::from_fee(deployment_transaction.fee_transition().unwrap()).unwrap(); + let rejected_deploy = ConfirmedTransaction::rejected_deploy(Uniform::rand(rng), fee, rejected, vec![]).unwrap(); + assert_eq!(rejected_deploy.to_unconfirmed_transaction_id().unwrap(), deployment_transaction.id()); + assert_eq!(rejected_deploy.to_unconfirmed_transaction().unwrap(), deployment_transaction); + + let deployment_transaction = + crate::transaction::test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), false, rng); let rejected = Rejected::new_deployment( *deployment_transaction.owner().unwrap(), deployment_transaction.deployment().unwrap().clone(), @@ -593,14 +683,17 @@ mod test { assert_eq!(rejected_deploy.to_unconfirmed_transaction().unwrap(), deployment_transaction); // Ensure that the unconfirmed transaction of a rejected execute is not equivalent to its confirmed transaction. - let execution_transaction = crate::transaction::test_helpers::sample_execution_transaction_with_fee(true, rng); + let execution_transaction = + crate::transaction::test_helpers::sample_execution_transaction_with_fee(true, rng, 0); let rejected = Rejected::new_execution(execution_transaction.execution().unwrap().clone()); let fee = Transaction::from_fee(execution_transaction.fee_transition().unwrap()).unwrap(); let rejected_execute = ConfirmedTransaction::rejected_execute(Uniform::rand(rng), fee, rejected, vec![]).unwrap(); assert_eq!(rejected_execute.to_unconfirmed_transaction_id().unwrap(), execution_transaction.id()); assert_eq!(rejected_execute.to_unconfirmed_transaction().unwrap(), execution_transaction); - let execution_transaction = crate::transaction::test_helpers::sample_execution_transaction_with_fee(false, rng); + + let execution_transaction = + crate::transaction::test_helpers::sample_execution_transaction_with_fee(false, rng, 0); let rejected = Rejected::new_execution(execution_transaction.execution().unwrap().clone()); let fee = Transaction::from_fee(execution_transaction.fee_transition().unwrap()).unwrap(); let rejected_execute = diff --git a/ledger/block/src/transactions/rejected/mod.rs b/ledger/block/src/transactions/rejected/mod.rs index dcf2abe04b..28a449af47 100644 --- a/ledger/block/src/transactions/rejected/mod.rs +++ b/ledger/block/src/transactions/rejected/mod.rs @@ -85,15 +85,13 @@ impl Rejected { /// When a transaction is rejected, its fee transition is used to construct the confirmed transaction ID, /// changing the original transaction ID. pub fn to_unconfirmed_id(&self, fee: &Option>) -> Result> { - let (tree, fee_index) = match self { - Self::Deployment(_, deployment) => (Transaction::deployment_tree(deployment)?, deployment.len()), - Self::Execution(execution) => (Transaction::execution_tree(execution)?, execution.len()), + // Compute the deployment or execution tree. + let tree = match self { + Self::Deployment(_, deployment) => Transaction::deployment_tree(deployment)?, + Self::Execution(execution) => Transaction::execution_tree(execution)?, }; - if let Some(fee) = fee { - Ok(*Transaction::transaction_tree(tree, fee_index, fee)?.root()) - } else { - Ok(*tree.root()) - } + // Construct the transaction tree and return the unconfirmed transaction ID. + Ok(*Transaction::transaction_tree(tree, fee.as_ref())?.root()) } } @@ -106,16 +104,21 @@ pub mod test_helpers { /// Samples a rejected deployment. pub(crate) fn sample_rejected_deployment( + version: u8, edition: u16, is_fee_private: bool, rng: &mut TestRng, ) -> Rejected { // Sample a deploy transaction. - let deployment = - match crate::transaction::test_helpers::sample_deployment_transaction(edition, is_fee_private, rng) { - Transaction::Deploy(_, _, _, deployment, _) => (*deployment).clone(), - _ => unreachable!(), - }; + let deployment = match crate::transaction::test_helpers::sample_deployment_transaction( + version, + edition, + is_fee_private, + rng, + ) { + Transaction::Deploy(_, _, _, deployment, _) => (*deployment).clone(), + _ => unreachable!(), + }; // Sample a new program owner. let private_key = PrivateKey::new(rng).unwrap(); @@ -130,7 +133,7 @@ pub mod test_helpers { pub(crate) fn sample_rejected_execution(is_fee_private: bool, rng: &mut TestRng) -> Rejected { // Sample an execute transaction. let execution = - match crate::transaction::test_helpers::sample_execution_transaction_with_fee(is_fee_private, rng) { + match crate::transaction::test_helpers::sample_execution_transaction_with_fee(is_fee_private, rng, 0) { Transaction::Execute(_, _, execution, _) => execution, _ => unreachable!(), }; @@ -144,8 +147,14 @@ pub mod test_helpers { let rng = &mut TestRng::default(); vec![ - sample_rejected_deployment(0, true, rng), - sample_rejected_deployment(0, false, rng), + sample_rejected_deployment(1, 0, true, rng), + sample_rejected_deployment(1, 0, false, rng), + sample_rejected_deployment(2, 0, true, rng), + sample_rejected_deployment(2, 0, false, rng), + sample_rejected_deployment(1, 1, true, rng), + sample_rejected_deployment(1, 1, false, rng), + sample_rejected_deployment(2, 1, true, rng), + sample_rejected_deployment(2, 1, false, rng), sample_rejected_execution(true, rng), sample_rejected_execution(false, rng), ] diff --git a/ledger/block/src/transactions/serialize.rs b/ledger/block/src/transactions/serialize.rs index 2b17710a20..cb88dfb204 100644 --- a/ledger/block/src/transactions/serialize.rs +++ b/ledger/block/src/transactions/serialize.rs @@ -75,21 +75,60 @@ mod tests { fn sample_transactions(index: u32, rng: &mut TestRng) -> Transactions { if index == 0 { [ - crate::transactions::confirmed::test_helpers::sample_accepted_deploy(0, true, rng), - crate::transactions::confirmed::test_helpers::sample_accepted_deploy(1, false, rng), + crate::transactions::confirmed::test_helpers::sample_accepted_deploy( + Uniform::rand(rng), + 1, + Uniform::rand(rng), + true, + rng, + ), + crate::transactions::confirmed::test_helpers::sample_accepted_deploy( + Uniform::rand(rng), + 1, + Uniform::rand(rng), + false, + rng, + ), + crate::transactions::confirmed::test_helpers::sample_accepted_deploy( + Uniform::rand(rng), + 2, + Uniform::rand(rng), + true, + rng, + ), + crate::transactions::confirmed::test_helpers::sample_accepted_deploy( + Uniform::rand(rng), + 2, + Uniform::rand(rng), + false, + rng, + ), ] .into_iter() .collect() } else if index == 1 { [ - crate::transactions::confirmed::test_helpers::sample_accepted_execute(0, true, rng), - crate::transactions::confirmed::test_helpers::sample_accepted_execute(1, false, rng), + crate::transactions::confirmed::test_helpers::sample_accepted_execute(Uniform::rand(rng), true, rng), + crate::transactions::confirmed::test_helpers::sample_accepted_execute(Uniform::rand(rng), false, rng), ] .into_iter() .collect() } else if index == 2 { [ - crate::transactions::confirmed::test_helpers::sample_accepted_deploy(0, true, rng), + crate::transactions::confirmed::test_helpers::sample_accepted_deploy( + Uniform::rand(rng), + 1, + Uniform::rand(rng), + true, + rng, + ), + crate::transactions::confirmed::test_helpers::sample_accepted_deploy( + Uniform::rand(rng), + 2, + Uniform::rand(rng), + true, + rng, + ), crate::transactions::confirmed::test_helpers::sample_accepted_execute(1, true, rng), crate::transactions::confirmed::test_helpers::sample_accepted_execute(2, false, rng), ] @@ -98,22 +137,87 @@ mod tests { } else if index == 3 { [ crate::transactions::confirmed::test_helpers::sample_accepted_execute(0, true, rng), - crate::transactions::confirmed::test_helpers::sample_accepted_deploy(1, true, rng), + crate::transactions::confirmed::test_helpers::sample_accepted_deploy( + Uniform::rand(rng), + 1, + Uniform::rand(rng), + true, + rng, + ), + crate::transactions::confirmed::test_helpers::sample_accepted_deploy( + Uniform::rand(rng), + 2, + Uniform::rand(rng), + true, + rng, + ), crate::transactions::confirmed::test_helpers::sample_rejected_execute(2, false, rng), - crate::transactions::confirmed::test_helpers::sample_rejected_deploy(3, 0, false, rng), + crate::transactions::confirmed::test_helpers::sample_rejected_deploy( + Uniform::rand(rng), + 1, + Uniform::rand(rng), + false, + rng, + ), + crate::transactions::confirmed::test_helpers::sample_rejected_deploy( + Uniform::rand(rng), + 2, + Uniform::rand(rng), + false, + rng, + ), ] .into_iter() .collect() } else { [ crate::transactions::confirmed::test_helpers::sample_accepted_execute(0, true, rng), - crate::transactions::confirmed::test_helpers::sample_rejected_deploy(1, 0, true, rng), - crate::transactions::confirmed::test_helpers::sample_accepted_deploy(2, true, rng), + crate::transactions::confirmed::test_helpers::sample_rejected_deploy( + Uniform::rand(rng), + 1, + Uniform::rand(rng), + true, + rng, + ), + crate::transactions::confirmed::test_helpers::sample_accepted_deploy( + Uniform::rand(rng), + 2, + Uniform::rand(rng), + true, + rng, + ), + crate::transactions::confirmed::test_helpers::sample_rejected_deploy( + Uniform::rand(rng), + 1, + Uniform::rand(rng), + true, + rng, + ), + crate::transactions::confirmed::test_helpers::sample_accepted_deploy( + Uniform::rand(rng), + 2, + Uniform::rand(rng), + true, + rng, + ), crate::transactions::confirmed::test_helpers::sample_rejected_execute(3, true, rng), crate::transactions::confirmed::test_helpers::sample_accepted_execute(4, false, rng), crate::transactions::confirmed::test_helpers::sample_rejected_execute(5, false, rng), crate::transactions::confirmed::test_helpers::sample_accepted_execute(6, false, rng), - crate::transactions::confirmed::test_helpers::sample_rejected_deploy(7, 0, false, rng), + crate::transactions::confirmed::test_helpers::sample_rejected_deploy( + Uniform::rand(rng), + 1, + Uniform::rand(rng), + false, + rng, + ), + crate::transactions::confirmed::test_helpers::sample_rejected_deploy( + Uniform::rand(rng), + 2, + Uniform::rand(rng), + false, + rng, + ), ] .into_iter() .collect() diff --git a/ledger/block/src/transition/input/mod.rs b/ledger/block/src/transition/input/mod.rs index 8b992686d1..e68443d5f2 100644 --- a/ledger/block/src/transition/input/mod.rs +++ b/ledger/block/src/transition/input/mod.rs @@ -190,7 +190,7 @@ pub(crate) mod test_helpers { let rng = &mut TestRng::default(); // Sample a transition. - let transaction = crate::transaction::test_helpers::sample_execution_transaction_with_fee(true, rng); + let transaction = crate::transaction::test_helpers::sample_execution_transaction_with_fee(true, rng, 0); let transition = transaction.transitions().next().unwrap(); // Retrieve the transition ID and input. diff --git a/ledger/block/src/transition/mod.rs b/ledger/block/src/transition/mod.rs index 38d7b139a4..fb5505ae1e 100644 --- a/ledger/block/src/transition/mod.rs +++ b/ledger/block/src/transition/mod.rs @@ -524,7 +524,7 @@ pub mod test_helpers { /// Samples a random transition. pub(crate) fn sample_transition(rng: &mut TestRng) -> Transition { if let Transaction::Execute(_, _, execution, _) = - crate::transaction::test_helpers::sample_execution_transaction_with_fee(true, rng) + crate::transaction::test_helpers::sample_execution_transaction_with_fee(true, rng, 0) { execution.into_transitions().next().unwrap() } else { diff --git a/ledger/block/src/transition/output/mod.rs b/ledger/block/src/transition/output/mod.rs index a068fc60b3..e1788f4e70 100644 --- a/ledger/block/src/transition/output/mod.rs +++ b/ledger/block/src/transition/output/mod.rs @@ -357,7 +357,7 @@ pub(crate) mod test_helpers { let rng = &mut TestRng::default(); // Sample a transition. - let transaction = crate::transaction::test_helpers::sample_execution_transaction_with_fee(true, rng); + let transaction = crate::transaction::test_helpers::sample_execution_transaction_with_fee(true, rng, 0); let transition = transaction.transitions().next().unwrap(); // Retrieve the transition ID and input. diff --git a/ledger/narwhal/data/src/lib.rs b/ledger/narwhal/data/src/lib.rs index 8322ed8061..a33d8d59ac 100644 --- a/ledger/narwhal/data/src/lib.rs +++ b/ledger/narwhal/data/src/lib.rs @@ -253,10 +253,12 @@ mod tests { // Sample transactions let transactions = [ - snarkvm_ledger_test_helpers::sample_deployment_transaction(0, true, rng), - snarkvm_ledger_test_helpers::sample_deployment_transaction(0, false, rng), - snarkvm_ledger_test_helpers::sample_execution_transaction_with_fee(true, rng), - snarkvm_ledger_test_helpers::sample_execution_transaction_with_fee(false, rng), + snarkvm_ledger_test_helpers::sample_deployment_transaction(1, Uniform::rand(rng), true, rng), + snarkvm_ledger_test_helpers::sample_deployment_transaction(1, Uniform::rand(rng), false, rng), + snarkvm_ledger_test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), true, rng), + snarkvm_ledger_test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), false, rng), + snarkvm_ledger_test_helpers::sample_execution_transaction_with_fee(true, rng, 0), + snarkvm_ledger_test_helpers::sample_execution_transaction_with_fee(false, rng, 0), snarkvm_ledger_test_helpers::sample_fee_private_transaction(rng), snarkvm_ledger_test_helpers::sample_fee_public_transaction(rng), ]; diff --git a/ledger/src/lib.rs b/ledger/src/lib.rs index 9d7ffec68a..affbd60f6d 100644 --- a/ledger/src/lib.rs +++ b/ledger/src/lib.rs @@ -497,6 +497,11 @@ pub(crate) mod test_helpers { pub(crate) type CurrentConsensusStore = ConsensusStore>; + #[cfg(not(feature = "rocks"))] + pub(crate) type CurrentConsensusStorage = snarkvm_ledger_store::helpers::memory::ConsensusMemory; + #[cfg(feature = "rocks")] + pub(crate) type CurrentConsensusStorage = snarkvm_ledger_store::helpers::rocksdb::ConsensusDB; + #[allow(dead_code)] pub(crate) struct TestEnv { pub ledger: CurrentLedger, diff --git a/ledger/src/tests.rs b/ledger/src/tests.rs index 90dfe4bfcc..a567aec3f7 100644 --- a/ledger/src/tests.rs +++ b/ledger/src/tests.rs @@ -17,7 +17,7 @@ use crate::{ Ledger, RecordsFilter, advance::split_candidate_solutions, - test_helpers::{CurrentAleo, CurrentLedger, CurrentNetwork}, + test_helpers::{CurrentAleo, CurrentConsensusStorage, CurrentLedger, CurrentNetwork}, }; use aleo_std::StorageMode; use console::{ @@ -31,7 +31,11 @@ use snarkvm_ledger_block::{Block, ConfirmedTransaction, Execution, Ratify, Rejec use snarkvm_ledger_committee::{Committee, MIN_VALIDATOR_STAKE}; use snarkvm_ledger_narwhal::{BatchCertificate, BatchHeader, Data, Subdag, Transmission, TransmissionID}; use snarkvm_ledger_store::ConsensusStore; -use snarkvm_synthesizer::{Stack, program::Program, vm::VM}; +use snarkvm_synthesizer::{ + Stack, + program::{Program, StackTrait}, + vm::VM, +}; use snarkvm_utilities::try_vm_runtime; use indexmap::{IndexMap, IndexSet}; @@ -297,6 +301,21 @@ impl TestChainBuilder { } } +// A helper function to create the cache key for a transaction in the partially-verified transactions cache. +fn create_cache_key( + vm: &VM, + transaction: &Transaction, +) -> (::TransactionID, Vec>) { + // Get the program editions. + let program_editions = transaction + .transitions() + .map(|transition| vm.process().read().get_stack(transition.program_id()).map(|stack| stack.program_edition())) + .collect::>>() + .unwrap(); + // Return the cache key. + (transaction.id(), program_editions) +} + #[test] fn test_load() { let rng = &mut TestRng::default(); @@ -1102,8 +1121,10 @@ fn test_execute_duplicate_input_ids() { let num_duplicate_deployments = 3; let mut executions = Vec::with_capacity(num_duplicate_deployments + 1); let mut execution_ids = Vec::with_capacity(num_duplicate_deployments + 1); + let mut execution_cache_keys = Vec::with_capacity(num_duplicate_deployments + 1); let mut deployments = Vec::with_capacity(num_duplicate_deployments); let mut deployment_ids = Vec::with_capacity(num_duplicate_deployments); + let mut deployment_cache_keys = Vec::with_capacity(num_duplicate_deployments); // Create Executions and Deployments, spending the same record. for i in 0..num_duplicate_deployments { @@ -1113,6 +1134,7 @@ fn test_execute_duplicate_input_ids() { .execute(&private_key, ("credits.aleo", "transfer_private"), inputs.clone().iter(), None, 0, None, rng) .unwrap(); execution_ids.push(execution.id()); + execution_cache_keys.push(create_cache_key(ledger.vm(), &execution)); executions.push(execution); // Deploy. let program_id = ProgramID::::from_str(&format!("dummy_program_{i}.aleo")).unwrap(); @@ -1131,6 +1153,7 @@ finalize foo: let deployment = ledger.vm.deploy(&private_key, &program, Some(record_deployment.clone()), 0, None, rng).unwrap(); deployment_ids.push(deployment.id()); + deployment_cache_keys.push(create_cache_key(ledger.vm(), &deployment)); deployments.push(deployment); } @@ -1149,6 +1172,7 @@ finalize foo: ) .unwrap(); execution_ids.push(execution.id()); + execution_cache_keys.push(create_cache_key(ledger.vm(), &execution)); executions.push(execution); // Select a transaction to mutate by a malicious validator. @@ -1183,12 +1207,14 @@ finalize foo: // Create a mutated transaction. let mutated_transaction = Transaction::from_execution(mutated_execution, Some(fee)).unwrap(); execution_ids.push(mutated_transaction.id()); + execution_cache_keys.push(create_cache_key(ledger.vm(), &mutated_transaction)); executions.push(mutated_transaction); // Create a mutated execution which just takes the fee transition, resulting in a different transaction id. // This simulates a malicious validator transforming a transaction to a fee transaction. let mutated_transaction = Transaction::from_fee(transaction_to_mutate.fee_transition().unwrap()).unwrap(); execution_ids.push(mutated_transaction.id()); + execution_cache_keys.push(create_cache_key(ledger.vm(), &mutated_transaction)); executions.push(mutated_transaction); // Create a block. @@ -1230,13 +1256,13 @@ finalize foo: // Ensure that verification was not run on aborted deployments. let partially_verified_transaction = ledger.vm().partially_verified_transactions().read().clone(); - assert!(partially_verified_transaction.contains(&execution_ids[2])); - assert!(partially_verified_transaction.contains(&deployment_ids[2])); - assert!(!partially_verified_transaction.contains(&execution_ids[1])); - assert!(!partially_verified_transaction.contains(&deployment_ids[1])); - assert!(!partially_verified_transaction.contains(&execution_ids[3])); - assert!(!partially_verified_transaction.contains(&execution_ids[4])); // Verification was run, but the execution was invalid. - assert!(!partially_verified_transaction.contains(&execution_ids[5])); + assert!(partially_verified_transaction.contains(&execution_cache_keys[2])); + assert!(partially_verified_transaction.contains(&deployment_cache_keys[2])); + assert!(!partially_verified_transaction.contains(&execution_cache_keys[1])); + assert!(!partially_verified_transaction.contains(&deployment_cache_keys[1])); + assert!(!partially_verified_transaction.contains(&execution_cache_keys[3])); + assert!(!partially_verified_transaction.contains(&execution_cache_keys[4])); // Verification was run, but the execution was invalid. + assert!(!partially_verified_transaction.contains(&execution_cache_keys[5])); // Prepare a transfer that will succeed for the subsequent block. let inputs = [Value::from_str(&format!("{address}")).unwrap(), Value::from_str("1000u64").unwrap()]; @@ -1245,6 +1271,7 @@ finalize foo: .execute(&private_key, ("credits.aleo", "transfer_public"), inputs.into_iter(), None, 0, None, rng) .unwrap(); let transfer_id = transfer.id(); + let transfer_cache_key = create_cache_key(ledger.vm(), &transfer); // Create a block. let block = ledger @@ -1270,9 +1297,9 @@ finalize foo: // Ensure that verification was not run on transactions aborted in a previous block. let partially_verified_transaction = ledger.vm().partially_verified_transactions().read().clone(); - assert!(partially_verified_transaction.contains(&transfer_id)); - assert!(!partially_verified_transaction.contains(&execution_ids[0])); - assert!(!partially_verified_transaction.contains(&deployment_ids[0])); + assert!(partially_verified_transaction.contains(&transfer_cache_key)); + assert!(!partially_verified_transaction.contains(&execution_cache_keys[0])); + assert!(!partially_verified_transaction.contains(&deployment_cache_keys[0])); } #[test] @@ -1417,14 +1444,17 @@ function create_duplicate_record: // Create the first transfer. let transfer_1 = create_execution_with_duplicate_output_id(1); let transfer_1_id = transfer_1.id(); + let transfer_1_cache_key = create_cache_key(ledger.vm(), &transfer_1); // Create a second transfer with the same output id. let transfer_2 = create_execution_with_duplicate_output_id(2); let transfer_2_id = transfer_2.id(); + let transfer_2_cache_key = create_cache_key(ledger.vm(), &transfer_2); // Create a third transfer with the same output id. let transfer_3 = create_execution_with_duplicate_output_id(3); let transfer_3_id = transfer_3.id(); + let transfer_3_cache_key = create_cache_key(ledger.vm(), &transfer_3); // Ensure that each transaction has a duplicate output id. let tx_1_output_id = transfer_1.output_ids().next().unwrap(); @@ -1436,14 +1466,17 @@ function create_duplicate_record: // Create the first deployment. let deployment_1 = create_deployment_with_duplicate_output_id(1); let deployment_1_id = deployment_1.id(); + let deployment_1_cache_key = create_cache_key(ledger.vm(), &deployment_1); // Create a second deployment with the same output id. let deployment_2 = create_deployment_with_duplicate_output_id(2); let deployment_2_id = deployment_2.id(); + let deployment_2_cache_key = create_cache_key(ledger.vm(), &deployment_2); // Create a third deployment with the same output id. let deployment_3 = create_deployment_with_duplicate_output_id(3); let deployment_3_id = deployment_3.id(); + let deployment_3_cache_key = create_cache_key(ledger.vm(), &deployment_3); // Ensure that each transaction has a duplicate output id. let deployment_1_output_id = deployment_1.output_ids().next().unwrap(); @@ -1476,10 +1509,10 @@ function create_duplicate_record: // Ensure that verification was not run on aborted deployments. let partially_verified_transaction = ledger.vm().partially_verified_transactions().read().clone(); - assert!(partially_verified_transaction.contains(&transfer_1_id)); - assert!(partially_verified_transaction.contains(&deployment_1_id)); - assert!(!partially_verified_transaction.contains(&transfer_2_id)); - assert!(!partially_verified_transaction.contains(&deployment_2_id)); + assert!(partially_verified_transaction.contains(&transfer_1_cache_key)); + assert!(partially_verified_transaction.contains(&deployment_1_cache_key)); + assert!(!partially_verified_transaction.contains(&transfer_2_cache_key)); + assert!(!partially_verified_transaction.contains(&deployment_2_cache_key)); // Prepare a transfer that will succeed for the subsequent block. let inputs = [Value::from_str(&format!("{address}")).unwrap(), Value::from_str("1000u64").unwrap()]; @@ -1488,6 +1521,7 @@ function create_duplicate_record: .execute(&private_key, ("credits.aleo", "transfer_public"), inputs.into_iter(), None, 0, None, rng) .unwrap(); let transfer_4_id = transfer_4.id(); + let transfer_4_cache_key = create_cache_key(ledger.vm(), &transfer_4); // Create a block. let block = ledger @@ -1513,9 +1547,9 @@ function create_duplicate_record: // Ensure that verification was not run on transactions aborted in a previous block. let partially_verified_transaction = ledger.vm().partially_verified_transactions().read().clone(); - assert!(partially_verified_transaction.contains(&transfer_4_id)); - assert!(!partially_verified_transaction.contains(&transfer_3_id)); - assert!(!partially_verified_transaction.contains(&deployment_3_id)); + assert!(partially_verified_transaction.contains(&transfer_4_cache_key)); + assert!(!partially_verified_transaction.contains(&transfer_3_cache_key)); + assert!(!partially_verified_transaction.contains(&deployment_3_cache_key)); } #[test] @@ -1594,14 +1628,17 @@ function empty_function: // Create the first transaction. let transaction_1 = create_transaction_with_duplicate_transition_id(); let transaction_1_id = transaction_1.id(); + let transaction_1_cache_key = create_cache_key(ledger.vm(), &transaction_1); // Create a second transaction with the same transition id. let transaction_2 = create_transaction_with_duplicate_transition_id(); let transaction_2_id = transaction_2.id(); + let transaction_2_cache_key = create_cache_key(ledger.vm(), &transaction_2); // Create a third transaction with the same transition_id let transaction_3 = create_transaction_with_duplicate_transition_id(); let transaction_3_id = transaction_3.id(); + let transaction_3_cache_key = create_cache_key(ledger.vm(), &transaction_3); // Ensure that each transaction has a duplicate transition id. let tx_1_transition_id = transaction_1.transition_ids().next().unwrap(); @@ -1628,8 +1665,8 @@ function empty_function: // Ensure that verification was not run on aborted transactions. let partially_verified_transaction = ledger.vm().partially_verified_transactions().read().clone(); - assert!(partially_verified_transaction.contains(&transaction_1_id)); - assert!(!partially_verified_transaction.contains(&transaction_2_id)); + assert!(partially_verified_transaction.contains(&transaction_1_cache_key)); + assert!(!partially_verified_transaction.contains(&transaction_2_cache_key)); // Prepare a transfer that will succeed for the subsequent block. let inputs = [Value::from_str(&format!("{address}")).unwrap(), Value::from_str("1000u64").unwrap()]; @@ -1638,6 +1675,7 @@ function empty_function: .execute(&private_key, ("credits.aleo", "transfer_public"), inputs.into_iter(), None, 0, None, rng) .unwrap(); let transfer_transaction_id = transfer_transaction.id(); + let transfer_cache_key = create_cache_key(ledger.vm(), &transfer_transaction); // Create a block. let block = ledger @@ -1663,8 +1701,8 @@ function empty_function: // Ensure that verification was not run on transactions aborted in a previous block. let partially_verified_transaction = ledger.vm().partially_verified_transactions().read().clone(); - assert!(partially_verified_transaction.contains(&transfer_transaction_id)); - assert!(!partially_verified_transaction.contains(&transaction_3_id)); + assert!(partially_verified_transaction.contains(&transfer_cache_key)); + assert!(!partially_verified_transaction.contains(&transaction_3_cache_key)); } #[test] @@ -1738,14 +1776,17 @@ function simple_output: // Create the first transaction. let transaction_1 = create_transaction_with_duplicate_tpk("empty_function"); let transaction_1_id = transaction_1.id(); + let transaction_1_cache_key = create_cache_key(ledger.vm(), &transaction_1); // Create a second transaction with the same tpk and tcm. let transaction_2 = create_transaction_with_duplicate_tpk("simple_output"); let transaction_2_id = transaction_2.id(); + let transaction_2_cache_key = create_cache_key(ledger.vm(), &transaction_2); // Create a third transaction with the same tpk and tcm. let transaction_3 = create_transaction_with_duplicate_tpk("simple_output"); let transaction_3_id = transaction_3.id(); + let transaction_3_cache_key = create_cache_key(ledger.vm(), &transaction_3); // Ensure that each transaction has a duplicate tcm and tpk. let tx_1_tpk = transaction_1.transitions().next().unwrap().tpk(); @@ -1778,8 +1819,8 @@ function simple_output: // Ensure that verification was not run on aborted transactions. let partially_verified_transaction = ledger.vm().partially_verified_transactions().read().clone(); - assert!(partially_verified_transaction.contains(&transaction_1_id)); - assert!(!partially_verified_transaction.contains(&transaction_2_id)); + assert!(partially_verified_transaction.contains(&transaction_1_cache_key)); + assert!(!partially_verified_transaction.contains(&transaction_2_cache_key)); // Prepare a transfer that will succeed for the subsequent block. let inputs = [Value::from_str(&format!("{address}")).unwrap(), Value::from_str("1000u64").unwrap()]; @@ -1788,6 +1829,7 @@ function simple_output: .execute(&private_key, ("credits.aleo", "transfer_public"), inputs.into_iter(), None, 0, None, rng) .unwrap(); let transfer_transaction_id = transfer_transaction.id(); + let transfer_transaction_cache_key = create_cache_key(ledger.vm(), &transfer_transaction); // Create a block. let block = ledger @@ -1813,8 +1855,8 @@ function simple_output: // Ensure that verification was not run on transactions aborted in a previous block. let partially_verified_transaction = ledger.vm().partially_verified_transactions().read().clone(); - assert!(partially_verified_transaction.contains(&transfer_transaction_id)); - assert!(!partially_verified_transaction.contains(&transaction_3_id)); + assert!(partially_verified_transaction.contains(&transfer_transaction_cache_key)); + assert!(!partially_verified_transaction.contains(&transaction_3_cache_key)); } #[test] @@ -1846,10 +1888,12 @@ function empty_function: // Create a deployment transaction for the first program with the same public payer. let deployment_1 = ledger.vm.deploy(&private_key, &program_1, None, 0, None, rng).unwrap(); let deployment_1_id = deployment_1.id(); + let deployment_1_cache_key = create_cache_key(ledger.vm(), &deployment_1); // Create a deployment transaction for the second program with the same public payer. let deployment_2 = ledger.vm.deploy(&private_key, &program_2, None, 0, None, rng).unwrap(); let deployment_2_id = deployment_2.id(); + let deployment_2_cache_key = create_cache_key(ledger.vm(), &deployment_2); // Create a block. let block = ledger @@ -1873,8 +1917,8 @@ function empty_function: // Ensure that verification was not run on aborted transactions. let partially_verified_transaction = ledger.vm().partially_verified_transactions().read().clone(); - assert!(partially_verified_transaction.contains(&deployment_1_id)); - assert!(!partially_verified_transaction.contains(&deployment_2_id)); + assert!(partially_verified_transaction.contains(&deployment_1_cache_key)); + assert!(!partially_verified_transaction.contains(&deployment_2_cache_key)); } #[test] @@ -2058,6 +2102,7 @@ finalize foo2: // Enforce that the block transactions were correct. assert_eq!(block.transactions().num_accepted(), 1); assert_eq!(block.transactions().num_rejected(), 1); + assert_eq!(block.aborted_transaction_ids().len(), 0); // Enforce that the first program was deployed and the second was rejected. assert_eq!(ledger.get_program(*program_1.id()).unwrap(), program_1); diff --git a/ledger/store/src/block/confirmed_tx_type/mod.rs b/ledger/store/src/block/confirmed_tx_type/mod.rs index f7d4b20ccc..2607337058 100644 --- a/ledger/store/src/block/confirmed_tx_type/mod.rs +++ b/ledger/store/src/block/confirmed_tx_type/mod.rs @@ -51,9 +51,9 @@ pub mod test_helpers { } /// Samples a rejected deploy. - pub(crate) fn sample_rejected_deploy(edition: u16, rng: &mut TestRng) -> ConfirmedTxType { + pub(crate) fn sample_rejected_deploy(version: u8, rng: &mut TestRng) -> ConfirmedTxType { // Sample the rejected deployment. - let rejected = snarkvm_ledger_test_helpers::sample_rejected_deployment(edition, rng.r#gen(), rng); + let rejected = snarkvm_ledger_test_helpers::sample_rejected_deployment(version, rng.r#gen(), rng.r#gen(), rng); // Return the rejected deploy. ConfirmedTxType::RejectedDeploy(rng.r#gen(), rejected) } @@ -73,7 +73,8 @@ pub mod test_helpers { vec![ sample_accepted_deploy(rng), sample_accepted_execution(rng), - sample_rejected_deploy(0, rng), + sample_rejected_deploy(1, rng), + sample_rejected_deploy(2, rng), sample_rejected_execute(rng), ] } diff --git a/ledger/store/src/helpers/memory/transaction.rs b/ledger/store/src/helpers/memory/transaction.rs index 517263f9e9..468dd66fb6 100644 --- a/ledger/store/src/helpers/memory/transaction.rs +++ b/ledger/store/src/helpers/memory/transaction.rs @@ -28,6 +28,7 @@ use crate::{ use console::{ prelude::*, program::{Identifier, ProgramID, ProgramOwner}, + types::U8, }; use snarkvm_synthesizer_program::Program; use snarkvm_synthesizer_snark::{Certificate, Proof, VerifyingKey}; @@ -102,6 +103,8 @@ pub struct DeploymentMemory { owner_map: MemoryMap<(ProgramID, u16), ProgramOwner>, /// The program map. program_map: MemoryMap<(ProgramID, u16), Program>, + /// The checksum map. + checksum_map: MemoryMap<(ProgramID, u16), [U8; 32]>, /// The verifying key map. verifying_key_map: MemoryMap<(ProgramID, Identifier, u16), VerifyingKey>, /// The certificate map. @@ -118,6 +121,7 @@ impl DeploymentStorage for DeploymentMemory { type ReverseIDMap = MemoryMap<(ProgramID, u16), N::TransactionID>; type OwnerMap = MemoryMap<(ProgramID, u16), ProgramOwner>; type ProgramMap = MemoryMap<(ProgramID, u16), Program>; + type ChecksumMap = MemoryMap<(ProgramID, u16), [U8; 32]>; type VerifyingKeyMap = MemoryMap<(ProgramID, Identifier, u16), VerifyingKey>; type CertificateMap = MemoryMap<(ProgramID, Identifier, u16), Certificate>; type FeeStorage = FeeMemory; @@ -131,6 +135,7 @@ impl DeploymentStorage for DeploymentMemory { reverse_id_map: MemoryMap::default(), owner_map: MemoryMap::default(), program_map: MemoryMap::default(), + checksum_map: MemoryMap::default(), verifying_key_map: MemoryMap::default(), certificate_map: MemoryMap::default(), fee_store, @@ -167,6 +172,11 @@ impl DeploymentStorage for DeploymentMemory { &self.program_map } + /// Returns the checksum map. + fn checksum_map(&self) -> &Self::ChecksumMap { + &self.checksum_map + } + /// Returns the verifying key map. fn verifying_key_map(&self) -> &Self::VerifyingKeyMap { &self.verifying_key_map diff --git a/ledger/store/src/helpers/rocksdb/internal/id.rs b/ledger/store/src/helpers/rocksdb/internal/id.rs index aeff41e1d4..1b3993ed9c 100644 --- a/ledger/store/src/helpers/rocksdb/internal/id.rs +++ b/ledger/store/src/helpers/rocksdb/internal/id.rs @@ -111,6 +111,7 @@ pub enum DeploymentMap { ReverseID = DataID::DeploymentReverseIDMap as u16, Owner = DataID::DeploymentOwnerMap as u16, Program = DataID::DeploymentProgramMap as u16, + Checksum = DataID::DeploymentChecksumMap as u16, VerifyingKey = DataID::DeploymentVerifyingKeyMap as u16, Certificate = DataID::DeploymentCertificateMap as u16, } @@ -300,6 +301,8 @@ enum DataID { OutputRecordSenderMap, // Track edition based on transaction ID IDEditionMap, + // Track deployments that contain an optional checksum + DeploymentChecksumMap, // Testing #[cfg(test)] diff --git a/ledger/store/src/helpers/rocksdb/transaction.rs b/ledger/store/src/helpers/rocksdb/transaction.rs index 694b397c19..4aeae03bd7 100644 --- a/ledger/store/src/helpers/rocksdb/transaction.rs +++ b/ledger/store/src/helpers/rocksdb/transaction.rs @@ -38,6 +38,7 @@ use crate::{ use console::{ prelude::*, program::{Identifier, ProgramID, ProgramOwner}, + types::U8, }; use snarkvm_synthesizer_program::Program; use snarkvm_synthesizer_snark::{Certificate, Proof, VerifyingKey}; @@ -112,6 +113,8 @@ pub struct DeploymentDB { owner_map: DataMap<(ProgramID, u16), ProgramOwner>, /// The program map. program_map: DataMap<(ProgramID, u16), Program>, + /// The checksum map. + checksum_map: DataMap<(ProgramID, u16), [U8; 32]>, /// The verifying key map. verifying_key_map: DataMap<(ProgramID, Identifier, u16), VerifyingKey>, /// The certificate map. @@ -128,6 +131,7 @@ impl DeploymentStorage for DeploymentDB { type ReverseIDMap = DataMap<(ProgramID, u16), N::TransactionID>; type OwnerMap = DataMap<(ProgramID, u16), ProgramOwner>; type ProgramMap = DataMap<(ProgramID, u16), Program>; + type ChecksumMap = DataMap<(ProgramID, u16), [U8; 32]>; type VerifyingKeyMap = DataMap<(ProgramID, Identifier, u16), VerifyingKey>; type CertificateMap = DataMap<(ProgramID, Identifier, u16), Certificate>; type FeeStorage = FeeDB; @@ -143,6 +147,7 @@ impl DeploymentStorage for DeploymentDB { reverse_id_map: rocksdb::RocksDB::open_map(N::ID, storage_mode.clone(), MapID::Deployment(DeploymentMap::ReverseID))?, owner_map: rocksdb::RocksDB::open_map(N::ID, storage_mode.clone(), MapID::Deployment(DeploymentMap::Owner))?, program_map: rocksdb::RocksDB::open_map(N::ID, storage_mode.clone(), MapID::Deployment(DeploymentMap::Program))?, + checksum_map: rocksdb::RocksDB::open_map(N::ID, storage_mode.clone(), MapID::Deployment(DeploymentMap::Checksum))?, verifying_key_map: rocksdb::RocksDB::open_map(N::ID, storage_mode.clone(), MapID::Deployment(DeploymentMap::VerifyingKey))?, certificate_map: rocksdb::RocksDB::open_map(N::ID, storage_mode.clone(), MapID::Deployment(DeploymentMap::Certificate))?, fee_store, @@ -179,6 +184,11 @@ impl DeploymentStorage for DeploymentDB { &self.program_map } + /// Returns the checksum map. + fn checksum_map(&self) -> &Self::ChecksumMap { + &self.checksum_map + } + /// Returns the verifying key map. fn verifying_key_map(&self) -> &Self::VerifyingKeyMap { &self.verifying_key_map diff --git a/ledger/store/src/program/finalize.rs b/ledger/store/src/program/finalize.rs index 115c08b943..52c813d9fb 100644 --- a/ledger/store/src/program/finalize.rs +++ b/ledger/store/src/program/finalize.rs @@ -578,11 +578,16 @@ impl> FinalizeStore { } impl> FinalizeStoreTrait for FinalizeStore { - /// Returns `true` if the given `program ID` and `mapping name` exist. + /// Returns `true` if the given `program ID` and `mapping name` is confirmed to exist. fn contains_mapping_confirmed(&self, program_id: &ProgramID, mapping_name: &Identifier) -> Result { self.storage.contains_mapping_confirmed(program_id, mapping_name) } + /// Returns `true` if the given `program ID` and `mapping name` exist. + fn contains_mapping_speculative(&self, program_id: &ProgramID, mapping_name: &Identifier) -> Result { + self.storage.contains_mapping_speculative(program_id, mapping_name) + } + /// Returns `true` if the given `program ID`, `mapping name`, and `key` exist. fn contains_key_speculative( &self, diff --git a/ledger/store/src/transaction/deployment.rs b/ledger/store/src/transaction/deployment.rs index bb35f49abd..c3a12e503c 100644 --- a/ledger/store/src/transaction/deployment.rs +++ b/ledger/store/src/transaction/deployment.rs @@ -22,6 +22,7 @@ use crate::{ use console::{ network::prelude::*, program::{Identifier, ProgramID, ProgramOwner}, + types::U8, }; use snarkvm_ledger_block::{Deployment, Fee, Transaction}; use snarkvm_synthesizer_program::Program; @@ -33,10 +34,19 @@ use core::marker::PhantomData; use std::borrow::Cow; /// A trait for deployment storage. +/// The deployment storage contains the `Deployment`s for all programs deployed on the network. +/// The storage has been migrated a few to times to support new features. +/// Here we describe the changes made to the storage and the invariants that must hold. +/// - **ConsensusVersion::V1..V7**: The deployment edition is always zero. The `IDEditionMap` and `ChecksumMap` did not exist. +/// - **ConsensusVersion::V8**: The deployment edition is either zero or one. The `IDEditionMap` is introduced and the `EditionMap` +/// is interpreted as the latest edition for the program ID. The `ChecksumMap` did not exist. +/// - **ConsensusVersion::V9**: The deployment edition can be any value from zero to `u16::MAX`. The `ChecksumMap` is introduced and +/// stores the program checksum, required in each deployment after `ConsensusVersion::V9`. pub trait DeploymentStorage: Clone + Send + Sync { /// The mapping of `transaction ID` to `program ID`. type IDMap: for<'a> Map<'a, N::TransactionID, ProgramID>; /// The mapping of `transaction ID` to `edition`. + /// This was introduced in `ConsensusVersion::V8`. type IDEditionMap: for<'a> Map<'a, N::TransactionID, u16>; /// The mapping of `program ID` to the **latest** `edition`. type EditionMap: for<'a> Map<'a, ProgramID, u16>; @@ -46,6 +56,9 @@ pub trait DeploymentStorage: Clone + Send + Sync { type OwnerMap: for<'a> Map<'a, (ProgramID, u16), ProgramOwner>; /// The mapping of `(program ID, edition)` to `program`. type ProgramMap: for<'a> Map<'a, (ProgramID, u16), Program>; + /// The mapping of `(program ID, edition)` to `checksum`. + /// This was introduced in `ConsensusVersion::V9`. + type ChecksumMap: for<'a> Map<'a, (ProgramID, u16), [U8; 32]>; /// The mapping of `(program ID, function name, edition)` to `verifying key`. type VerifyingKeyMap: for<'a> Map<'a, (ProgramID, Identifier, u16), VerifyingKey>; /// The mapping of `(program ID, function name, edition)` to `certificate`. @@ -68,6 +81,8 @@ pub trait DeploymentStorage: Clone + Send + Sync { fn owner_map(&self) -> &Self::OwnerMap; /// Returns the program map. fn program_map(&self) -> &Self::ProgramMap; + /// Returns the checksum map. + fn checksum_map(&self) -> &Self::ChecksumMap; /// Returns the verifying key map. fn verifying_key_map(&self) -> &Self::VerifyingKeyMap; /// Returns the certificate map. @@ -88,6 +103,7 @@ pub trait DeploymentStorage: Clone + Send + Sync { self.reverse_id_map().start_atomic(); self.owner_map().start_atomic(); self.program_map().start_atomic(); + self.checksum_map().start_atomic(); self.verifying_key_map().start_atomic(); self.certificate_map().start_atomic(); self.fee_store().start_atomic(); @@ -101,6 +117,7 @@ pub trait DeploymentStorage: Clone + Send + Sync { || self.reverse_id_map().is_atomic_in_progress() || self.owner_map().is_atomic_in_progress() || self.program_map().is_atomic_in_progress() + || self.checksum_map().is_atomic_in_progress() || self.verifying_key_map().is_atomic_in_progress() || self.certificate_map().is_atomic_in_progress() || self.fee_store().is_atomic_in_progress() @@ -114,6 +131,7 @@ pub trait DeploymentStorage: Clone + Send + Sync { self.reverse_id_map().atomic_checkpoint(); self.owner_map().atomic_checkpoint(); self.program_map().atomic_checkpoint(); + self.checksum_map().atomic_checkpoint(); self.verifying_key_map().atomic_checkpoint(); self.certificate_map().atomic_checkpoint(); self.fee_store().atomic_checkpoint(); @@ -127,6 +145,7 @@ pub trait DeploymentStorage: Clone + Send + Sync { self.reverse_id_map().clear_latest_checkpoint(); self.owner_map().clear_latest_checkpoint(); self.program_map().clear_latest_checkpoint(); + self.checksum_map().clear_latest_checkpoint(); self.verifying_key_map().clear_latest_checkpoint(); self.certificate_map().clear_latest_checkpoint(); self.fee_store().clear_latest_checkpoint(); @@ -140,6 +159,7 @@ pub trait DeploymentStorage: Clone + Send + Sync { self.reverse_id_map().atomic_rewind(); self.owner_map().atomic_rewind(); self.program_map().atomic_rewind(); + self.checksum_map().atomic_rewind(); self.verifying_key_map().atomic_rewind(); self.certificate_map().atomic_rewind(); self.fee_store().atomic_rewind(); @@ -153,6 +173,7 @@ pub trait DeploymentStorage: Clone + Send + Sync { self.reverse_id_map().abort_atomic(); self.owner_map().abort_atomic(); self.program_map().abort_atomic(); + self.checksum_map().abort_atomic(); self.verifying_key_map().abort_atomic(); self.certificate_map().abort_atomic(); self.fee_store().abort_atomic(); @@ -166,6 +187,7 @@ pub trait DeploymentStorage: Clone + Send + Sync { self.reverse_id_map().finish_atomic()?; self.owner_map().finish_atomic()?; self.program_map().finish_atomic()?; + self.checksum_map().finish_atomic()?; self.verifying_key_map().finish_atomic()?; self.certificate_map().finish_atomic()?; self.fee_store().finish_atomic() @@ -192,6 +214,8 @@ pub trait DeploymentStorage: Clone + Send + Sync { let program = deployment.program(); // Retrieve the program ID. let program_id = *program.id(); + // Retrieve the checksum. + let checksum = deployment.program_checksum(); // If the deployment edition is greater than zero, then ensure that it increments the latest edition for the program ID. let expected_edition = match self.get_latest_edition_for_program(&program_id)? { @@ -215,11 +239,18 @@ pub trait DeploymentStorage: Clone + Send + Sync { self.owner_map().insert((program_id, edition), *owner)?; // Store the program. self.program_map().insert((program_id, edition), program.clone())?; + // Store the edition in the ID edition map. // Note: Prior to `ConsensusVersion::V8`, the edition is always zero. // `ConsensusVersion::V8` allows the edition to be one via a one-time redeployment. + // `ConsensusVersion::V9` introduces upgradability which allows editions to be incremented up to `u16::MAX` self.id_edition_map().insert(*transaction_id, edition)?; + // If the checksum exists, then store it into the `ChecksumMap`. + if let Some(checksum) = checksum { + self.checksum_map().insert((program_id, edition), checksum)?; + } + // Store the verifying keys and certificates. for (function_name, (verifying_key, certificate)) in deployment.verifying_keys() { // Store the verifying key. @@ -285,6 +316,8 @@ pub trait DeploymentStorage: Clone + Send + Sync { self.owner_map().remove(&(program_id, edition))?; // Remove the program. self.program_map().remove(&(program_id, edition))?; + // Remove the checksum. + self.checksum_map().remove(&(program_id, edition))?; // Remove the verifying keys and certificates. for function_name in program.functions().keys() { @@ -376,12 +409,12 @@ pub trait DeploymentStorage: Clone + Send + Sync { Some(edition) => Ok(Some(*edition)), None => { // Check if the program exists in the store. - if self.get_program_id(transaction_id)?.is_none() { - return Ok(None); - }; - // Prior to `ConsensusVersion::V8`, if a program is not in the `IDEditionMap` but exists, - // then it must have been deployed when editions were exclusively zero. - Ok(Some(0)) + match self.get_program_id(transaction_id)?.is_none() { + true => Ok(None), + // If a program is not in the `IDEditionMap` but exists in the store, + // then it must have been deployed prior to `ConsensusVersion::V8` when editions were exclusively zero. + false => Ok(Some(0)), + } } } } @@ -542,6 +575,19 @@ pub trait DeploymentStorage: Clone + Send + Sync { let Some(program) = self.program_map().get_confirmed(&(program_id, edition))?.map(|x| x.into_owned()) else { bail!("Failed to get the deployed program '{program_id}' (edition {edition})"); }; + // Retrieve the checksum. + let program_checksum = + self.checksum_map().get_confirmed(&(program_id, edition))?.map(|checksum| checksum.into_owned()); + // If the checksum is present, then retrieve the owner address. + // Note: This is done to ensure that `Deployment` is consistent. Both the checksum and owner must be present or absent. + // This invariant is also enforced in `check_transaction`. + let program_owner = match program_checksum.is_some() { + false => None, + true => match self.owner_map().get_confirmed(&(program_id, edition))? { + Some(owner) => Some(owner.address()), + None => bail!("Failed to get the owner for program '{program_id}' (edition {edition})"), + }, + }; // Initialize a vector for the verifying keys and certificates. let mut verifying_keys = Vec::with_capacity(program.functions().len()); @@ -565,7 +611,7 @@ pub trait DeploymentStorage: Clone + Send + Sync { } // Return the deployment. - Ok(Some(Deployment::new(edition, program, verifying_keys)?)) + Ok(Some(Deployment::new(edition, program, verifying_keys, program_checksum, program_owner)?)) } /// Returns the fee for the given `transaction ID`. @@ -834,7 +880,7 @@ impl> DeploymentStore { self.storage.edition_map().contains_key_confirmed(program_id) } - /// Returns `true` if the given program ID and edition exists. + /// Returns `true` if the given program ID and edition exist. pub fn contains_program_id_and_edition(&self, program_id: &ProgramID, edition: u16) -> Result { self.storage.reverse_id_map().contains_key_confirmed(&(*program_id, edition)) } @@ -907,13 +953,17 @@ mod tests { let deployment_store = DeploymentMemory::open(fee_store).unwrap(); // Sample the transactions. - let transaction_0 = snarkvm_ledger_test_helpers::sample_deployment_transaction(0, true, rng); - let transaction_1 = snarkvm_ledger_test_helpers::sample_deployment_transaction(1, false, rng); - let transactions = vec![transaction_0, transaction_1]; + let transaction_0 = snarkvm_ledger_test_helpers::sample_deployment_transaction(1, 0, true, rng); + let transaction_1 = snarkvm_ledger_test_helpers::sample_deployment_transaction(1, 1, false, rng); + let transaction_2 = snarkvm_ledger_test_helpers::sample_deployment_transaction(2, 0, true, rng); + let transaction_3 = snarkvm_ledger_test_helpers::sample_deployment_transaction(2, 1, false, rng); + let transaction_4 = snarkvm_ledger_test_helpers::sample_deployment_transaction(2, 2, true, rng); + let transactions = vec![transaction_0, transaction_1, transaction_2, transaction_3, transaction_4]; for transaction in transactions { let transaction_id = transaction.id(); let program_id = *transaction.deployment().unwrap().program_id(); + let checksum = transaction.deployment().unwrap().program_checksum(); let edition = transaction.deployment().unwrap().edition(); // Ensure the deployment transaction does not exist. @@ -923,6 +973,19 @@ mod tests { // Insert the deployment transaction. deployment_store.insert(&transaction).unwrap(); + // If the deployment has a checksum, then check that it exists in the checksum map. + match checksum { + Some(checksum) => { + let candidate = deployment_store.checksum_map().get_confirmed(&(program_id, edition)).unwrap(); + assert_eq!(Some(checksum), candidate.map(|c| c.into_owned())); + } + None => { + let candidate = deployment_store.checksum_map().get_confirmed(&(program_id, edition)).unwrap(); + assert_eq!(None, candidate); + } + } + + // Check that the transaction exists in the ID edition map let candidate = deployment_store.id_edition_map().get_confirmed(&transaction_id).unwrap(); assert_eq!(Some(edition), candidate.map(|e| *e)); @@ -938,6 +1001,10 @@ mod tests { let actual = deployment_store.get_latest_edition_for_program(&program_id).unwrap(); assert_eq!(Some(edition), actual); + // Retrieve the latest edition for the transaction ID and verify that it matches. + let actual = deployment_store.get_edition_for_transaction(&transaction_id).unwrap(); + assert_eq!(Some(edition), actual); + // Remove the deployment. deployment_store.remove(&transaction_id).unwrap(); @@ -976,9 +1043,12 @@ mod tests { let deployment_store = DeploymentMemory::open(fee_store).unwrap(); // Sample the transactions. - let transaction_0 = snarkvm_ledger_test_helpers::sample_deployment_transaction(0, true, rng); - let transaction_1 = snarkvm_ledger_test_helpers::sample_deployment_transaction(1, false, rng); - let transactions = vec![transaction_0, transaction_1]; + let transaction_0 = snarkvm_ledger_test_helpers::sample_deployment_transaction(1, 0, true, rng); + let transaction_1 = snarkvm_ledger_test_helpers::sample_deployment_transaction(1, 1, false, rng); + let transaction_2 = snarkvm_ledger_test_helpers::sample_deployment_transaction(2, 0, true, rng); + let transaction_3 = snarkvm_ledger_test_helpers::sample_deployment_transaction(2, 1, false, rng); + let transaction_4 = snarkvm_ledger_test_helpers::sample_deployment_transaction(2, 2, true, rng); + let transactions = vec![transaction_0, transaction_1, transaction_2, transaction_3, transaction_4]; for transaction in transactions { let transaction_id = transaction.id(); @@ -986,27 +1056,46 @@ mod tests { Transaction::Deploy(_, _, _, ref deployment, _) => (*deployment.program_id(), deployment.edition()), _ => panic!("Incorrect transaction type"), }; + let fee_id = *transaction.fee_transition().unwrap().id(); // Ensure the deployment transaction does not exist. let candidate = deployment_store.get_transaction(&transaction_id).unwrap(); assert_eq!(None, candidate); + // A helper to test the `find_*` methods. + let test_find_methods = |program_exists: bool, transaction_exists: bool| { + // Find the latest transaction ID from the program ID. + let candidate_0 = deployment_store.find_latest_transaction_id_from_program_id(&program_id).unwrap(); + // Find the transaction ID from the program ID and edition. + let candidate_1 = + deployment_store.find_transaction_id_from_program_id_and_edition(&program_id, edition).unwrap(); + // Find the transaction ID from the transition ID. + let candidate_2 = deployment_store.find_transaction_id_from_transition_id(&fee_id).unwrap(); + + // If the program exists, then the latest transaction ID should be found. + assert_eq!(program_exists, candidate_0.is_some()); + // If the transaction exists, then the transaction ID should be found. + assert_eq!(transaction_exists, candidate_1.is_some()); + assert_eq!(candidate_1, candidate_2); + }; + // If the edition is zero, then check that a transaction is not found. // Otherwise, check that the transaction is found. if edition == 0 { - let candidate = deployment_store.find_latest_transaction_id_from_program_id(&program_id).unwrap(); - assert_eq!(None, candidate); + test_find_methods(false, false); } else { - let candidate = deployment_store.find_latest_transaction_id_from_program_id(&program_id).unwrap(); - assert!(candidate.is_some()); + test_find_methods(true, false); } // Insert the deployment. deployment_store.insert(&transaction).unwrap(); + // Get the transaction again. + let candidate = deployment_store.get_transaction(&transaction_id).unwrap(); + assert_eq!(Some(transaction.clone()), candidate); + // Find the transaction ID. - let candidate = deployment_store.find_latest_transaction_id_from_program_id(&program_id).unwrap(); - assert_eq!(Some(transaction_id), candidate); + test_find_methods(true, true); // Remove the deployment. deployment_store.remove(&transaction_id).unwrap(); @@ -1014,11 +1103,9 @@ mod tests { // If the edition is zero, then check that a transaction is not found. // Otherwise, check that the transaction is found. if edition == 0 { - let candidate = deployment_store.find_latest_transaction_id_from_program_id(&program_id).unwrap(); - assert_eq!(None, candidate); + test_find_methods(false, false); } else { - let candidate = deployment_store.find_latest_transaction_id_from_program_id(&program_id).unwrap(); - assert!(candidate.is_some()); + test_find_methods(true, false); } // Insert the deployment again. diff --git a/ledger/store/src/transaction/execution.rs b/ledger/store/src/transaction/execution.rs index 2dc9e97311..23b4fa51f8 100644 --- a/ledger/store/src/transaction/execution.rs +++ b/ledger/store/src/transaction/execution.rs @@ -471,11 +471,11 @@ mod tests { let rng = &mut TestRng::default(); // Sample the execution transaction. - let transaction = snarkvm_ledger_test_helpers::sample_execution_transaction_with_fee(true, rng); + let transaction = snarkvm_ledger_test_helpers::sample_execution_transaction_with_fee(true, rng, 0); insert_get_remove(transaction).unwrap(); // Sample the execution transaction. - let transaction = snarkvm_ledger_test_helpers::sample_execution_transaction_with_fee(false, rng); + let transaction = snarkvm_ledger_test_helpers::sample_execution_transaction_with_fee(false, rng, 0); insert_get_remove(transaction).unwrap(); } @@ -484,11 +484,11 @@ mod tests { let rng = &mut TestRng::default(); // Sample the execution transaction. - let transaction = snarkvm_ledger_test_helpers::sample_execution_transaction_with_fee(true, rng); + let transaction = snarkvm_ledger_test_helpers::sample_execution_transaction_with_fee(true, rng, 0); find_transaction_id(transaction).unwrap(); // Sample the execution transaction. - let transaction = snarkvm_ledger_test_helpers::sample_execution_transaction_with_fee(false, rng); + let transaction = snarkvm_ledger_test_helpers::sample_execution_transaction_with_fee(false, rng, 0); find_transaction_id(transaction).unwrap(); } } diff --git a/ledger/store/src/transaction/mod.rs b/ledger/store/src/transaction/mod.rs index 7a696de25e..9ddec8b772 100644 --- a/ledger/store/src/transaction/mod.rs +++ b/ledger/store/src/transaction/mod.rs @@ -476,7 +476,7 @@ impl> TransactionStore { self.storage.deployment_store().contains_program_id(program_id) } - /// Returns `true` if the given program ID and edition exists. + /// Returns `true` if the given program ID and edition exist. pub fn contains_program_id_and_edition(&self, program_id: &ProgramID, edition: u16) -> Result { self.storage.deployment_store().contains_program_id_and_edition(program_id, edition) } @@ -545,23 +545,25 @@ mod tests { fn test_insert_get_remove() { let rng = &mut TestRng::default(); + // Initialize a new transition store. + let transition_store = TransitionStore::<_, TransitionMemory<_>>::open(StorageMode::new_test(None)).unwrap(); + // Initialize a new transaction store. + let transaction_store = TransactionStore::<_, TransactionMemory<_>>::open(transition_store).unwrap(); + // Sample the transactions. for transaction in [ - snarkvm_ledger_test_helpers::sample_deployment_transaction(0, true, rng), - snarkvm_ledger_test_helpers::sample_deployment_transaction(0, false, rng), - snarkvm_ledger_test_helpers::sample_execution_transaction_with_fee(true, rng), - snarkvm_ledger_test_helpers::sample_execution_transaction_with_fee(false, rng), + snarkvm_ledger_test_helpers::sample_deployment_transaction(1, 0, true, rng), + snarkvm_ledger_test_helpers::sample_deployment_transaction(1, 1, false, rng), + snarkvm_ledger_test_helpers::sample_deployment_transaction(2, 0, true, rng), + snarkvm_ledger_test_helpers::sample_deployment_transaction(2, 1, false, rng), + snarkvm_ledger_test_helpers::sample_deployment_transaction(2, 2, true, rng), + snarkvm_ledger_test_helpers::sample_execution_transaction_with_fee(true, rng, 0), + snarkvm_ledger_test_helpers::sample_execution_transaction_with_fee(false, rng, 0), snarkvm_ledger_test_helpers::sample_fee_private_transaction(rng), snarkvm_ledger_test_helpers::sample_fee_public_transaction(rng), ] { let transaction_id = transaction.id(); - // Initialize a new transition store. - let transition_store = - TransitionStore::<_, TransitionMemory<_>>::open(StorageMode::new_test(None)).unwrap(); - // Initialize a new transaction store. - let transaction_store = TransactionStore::<_, TransactionMemory<_>>::open(transition_store).unwrap(); - // Ensure the transaction does not exist. let candidate = transaction_store.get_transaction(&transaction_id).unwrap(); assert_eq!(None, candidate); @@ -571,7 +573,7 @@ mod tests { // Retrieve the transaction. let candidate = transaction_store.get_transaction(&transaction_id).unwrap(); - assert_eq!(Some(transaction), candidate); + assert_eq!(Some(transaction.clone()), candidate); // Remove the transaction. transaction_store.remove(&transaction_id).unwrap(); @@ -579,6 +581,9 @@ mod tests { // Ensure the transaction does not exist. let candidate = transaction_store.get_transaction(&transaction_id).unwrap(); assert_eq!(None, candidate); + + // Insert the transaction again. + transaction_store.insert(&transaction).unwrap(); } } @@ -586,30 +591,40 @@ mod tests { fn test_find_transaction_id() { let rng = &mut TestRng::default(); + // Initialize a new transition store. + let transition_store = TransitionStore::<_, TransitionMemory<_>>::open(StorageMode::new_test(None)).unwrap(); + // Initialize a new transaction store. + let transaction_store = TransactionStore::<_, TransactionMemory<_>>::open(transition_store).unwrap(); + // Sample the transactions. for transaction in [ - snarkvm_ledger_test_helpers::sample_deployment_transaction(0, true, rng), - snarkvm_ledger_test_helpers::sample_deployment_transaction(0, false, rng), - snarkvm_ledger_test_helpers::sample_execution_transaction_with_fee(true, rng), - snarkvm_ledger_test_helpers::sample_execution_transaction_with_fee(false, rng), - snarkvm_ledger_test_helpers::sample_fee_private_transaction(rng), - snarkvm_ledger_test_helpers::sample_fee_public_transaction(rng), + snarkvm_ledger_test_helpers::sample_deployment_transaction(1, 0, true, rng), + snarkvm_ledger_test_helpers::sample_deployment_transaction(1, 1, false, rng), + snarkvm_ledger_test_helpers::sample_deployment_transaction(2, 0, true, rng), + snarkvm_ledger_test_helpers::sample_deployment_transaction(2, 1, false, rng), + snarkvm_ledger_test_helpers::sample_deployment_transaction(2, 2, true, rng), + snarkvm_ledger_test_helpers::sample_execution_transaction_with_fee(true, rng, 0), + snarkvm_ledger_test_helpers::sample_execution_transaction_with_fee(true, rng, 1), + Transaction::from_fee(snarkvm_ledger_test_helpers::sample_fee_private( + snarkvm_console::types::Field::rand(rng), + rng, + )) + .unwrap(), + Transaction::from_fee(snarkvm_ledger_test_helpers::sample_fee_public( + snarkvm_console::types::Field::rand(rng), + rng, + )) + .unwrap(), ] { let transaction_id = transaction.id(); let transition_ids = transaction.transition_ids(); - // Initialize a new transition store. - let transition_store = - TransitionStore::<_, TransitionMemory<_>>::open(StorageMode::new_test(None)).unwrap(); - // Initialize a new transaction store. - let transaction_store = TransactionStore::<_, TransactionMemory<_>>::open(transition_store).unwrap(); - // Ensure the execution transaction does not exist. let candidate = transaction_store.get_transaction(&transaction_id).unwrap(); assert_eq!(None, candidate); for transition_id in transition_ids { - // Ensure the transaction ID is not found. + // Ensure the transition ID is not found. let candidate = transaction_store.find_transaction_id_from_transition_id(transition_id).unwrap(); assert_eq!(None, candidate); @@ -627,6 +642,24 @@ mod tests { let candidate = transaction_store.find_transaction_id_from_transition_id(transition_id).unwrap(); assert_eq!(None, candidate); } + + // Insert the transaction. + transaction_store.insert(&transaction).unwrap(); + + // If the transaction was a deployment, find it through the other getters. + if let Some(deployment) = transaction.deployment() { + // Get the program ID. + let program_id = deployment.program().id(); + // Get the edition. + let edition = deployment.edition(); + // Get and check the latest transaction ID for the program ID. + let candidate = transaction_store.find_latest_transaction_id_from_program_id(program_id).unwrap(); + assert_eq!(Some(transaction_id), candidate); + // Get the check the transaction ID for the program ID and edition. + let candidate = + transaction_store.find_transaction_id_from_program_id_and_edition(program_id, edition).unwrap(); + assert_eq!(Some(transaction_id), candidate); + } } } } diff --git a/ledger/store/src/transition/mod.rs b/ledger/store/src/transition/mod.rs index 830da708fe..328e48da0a 100644 --- a/ledger/store/src/transition/mod.rs +++ b/ledger/store/src/transition/mod.rs @@ -653,8 +653,8 @@ mod tests { let rng = &mut TestRng::default(); // Sample the transactions. - let transaction_0 = snarkvm_ledger_test_helpers::sample_execution_transaction_with_fee(true, rng); - let transaction_1 = snarkvm_ledger_test_helpers::sample_execution_transaction_with_fee(false, rng); + let transaction_0 = snarkvm_ledger_test_helpers::sample_execution_transaction_with_fee(true, rng, 0); + let transaction_1 = snarkvm_ledger_test_helpers::sample_execution_transaction_with_fee(false, rng, 0); let transactions = vec![transaction_0, transaction_1]; for transaction in transactions { diff --git a/ledger/test-helpers/src/lib.rs b/ledger/test-helpers/src/lib.rs index fbbabbb2ea..88babe2072 100644 --- a/ledger/test-helpers/src/lib.rs +++ b/ledger/test-helpers/src/lib.rs @@ -53,7 +53,7 @@ type CurrentAleo = circuit::network::AleoV0; /// Samples a random transition. pub fn sample_transition(rng: &mut TestRng) -> Transition { - crate::sample_execution(rng).into_transitions().next().unwrap() + crate::sample_execution(rng, 0).into_transitions().next().unwrap() } /// Sample the transition inputs. @@ -61,7 +61,7 @@ pub fn sample_inputs() -> Vec<(::TransitionID, Input< let rng = &mut TestRng::default(); // Sample a transition. - let transaction = crate::sample_execution_transaction_with_fee(true, rng); + let transaction = crate::sample_execution_transaction_with_fee(true, rng, 0); let transition = transaction.transitions().next().unwrap(); // Retrieve the transition ID and input. @@ -94,7 +94,7 @@ pub fn sample_outputs() -> Vec<(::TransitionID, Outpu let rng = &mut TestRng::default(); // Sample a transition. - let transaction = crate::sample_execution_transaction_with_fee(true, rng); + let transaction = crate::sample_execution_transaction_with_fee(true, rng, 0); let transition = transaction.transitions().next().unwrap(); // Retrieve the transition ID and input. @@ -141,14 +141,14 @@ pub fn sample_outputs() -> Vec<(::TransitionID, Outpu /******************************************* Deployment *******************************************/ -pub fn sample_deployment(rng: &mut TestRng) -> Deployment { +pub fn sample_deployment_v1(edition: u16, rng: &mut TestRng) -> Deployment { static INSTANCE: OnceLock> = OnceLock::new(); - INSTANCE + let deployment = INSTANCE .get_or_init(|| { // Initialize a new program. let (string, program) = Program::::parse( r" -program testing.aleo; +program testing_one.aleo; mapping store: key as u32.public; @@ -161,22 +161,83 @@ function compute: ) .unwrap(); assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'"); + // Construct the process. + let process = Process::load().unwrap(); + // Compute the deployment. + let mut deployment = process.deploy::(&program, rng).unwrap(); + // Unset the checksum. + deployment.set_program_checksum_raw(None); + // Unset the owner. + deployment.set_program_owner_raw(None); + // Return the deployment. + // Note: This is a testing-only hack to adhere to Rust's dependency cycle rules. + Deployment::from_str(&deployment.to_string()).unwrap() + }) + .clone(); + // Create a new deployment with the desired edition. + Deployment::::new( + edition % 2, + deployment.program().clone(), + deployment.verifying_keys().clone(), + deployment.program_checksum(), + deployment.program_owner(), + ) + .unwrap() +} +pub fn sample_deployment_v2(edition: u16, rng: &mut TestRng) -> Deployment { + static INSTANCE: OnceLock> = OnceLock::new(); + let deployment = INSTANCE + .get_or_init(|| { + // Initialize a new program. + let (string, program) = Program::::parse( + r" +program testing_two.aleo; + +mapping store: + key as u32.public; + value as u32.public; + +function compute: + input r0 as u32.private; + add r0 r0 into r1; + output r1 as u32.public;", + ) + .unwrap(); + assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'"); // Construct the process. let process = Process::load().unwrap(); // Compute the deployment. - let deployment = process.deploy::(&program, rng).unwrap(); + let mut deployment = process.deploy::(&program, rng).unwrap(); + // Set the program checksum. + deployment.set_program_checksum_raw(Some(deployment.program().to_checksum())); + // Set the program owner. + deployment.set_program_owner_raw(Some(Address::rand(rng))); // Return the deployment. // Note: This is a testing-only hack to adhere to Rust's dependency cycle rules. Deployment::from_str(&deployment.to_string()).unwrap() }) - .clone() + .clone(); + // Create a new deployment with the desired edition. + Deployment::::new( + edition, + deployment.program().clone(), + deployment.verifying_keys().clone(), + deployment.program_checksum(), + deployment.program_owner(), + ) + .unwrap() } /// Samples a rejected deployment. -pub fn sample_rejected_deployment(edition: u16, is_fee_private: bool, rng: &mut TestRng) -> Rejected { +pub fn sample_rejected_deployment( + version: u8, + edition: u16, + is_fee_private: bool, + rng: &mut TestRng, +) -> Rejected { // Sample a deploy transaction. - let deployment = match crate::sample_deployment_transaction(edition, is_fee_private, rng) { + let deployment = match crate::sample_deployment_transaction(version, edition, is_fee_private, rng) { Transaction::Deploy(_, _, _, deployment, _) => (*deployment).clone(), _ => unreachable!(), }; @@ -193,19 +254,23 @@ pub fn sample_rejected_deployment(edition: u16, is_fee_private: bool, rng: &mut /******************************************* Execution ********************************************/ /// Samples a random execution. -pub fn sample_execution(rng: &mut TestRng) -> Execution { +pub fn sample_execution(rng: &mut TestRng, index: usize) -> Execution { // Sample the genesis block. let block = crate::sample_genesis_block(rng); // Retrieve a transaction. - let transaction = block.transactions().iter().next().unwrap().deref().clone(); + let transaction = block.transactions().iter().nth(index).unwrap().deref().clone(); // Retrieve the execution. - if let Transaction::Execute(_, _, execution, _) = transaction { *execution } else { unreachable!() } + if let Transaction::Execute(_, _, execution, _) = transaction { + *execution + } else { + panic!("Index {index} exceeded the number of executions in the genesis block") + } } /// Samples a rejected execution. pub fn sample_rejected_execution(is_fee_private: bool, rng: &mut TestRng) -> Rejected { // Sample an execute transaction. - let execution = match crate::sample_execution_transaction_with_fee(is_fee_private, rng) { + let execution = match crate::sample_execution_transaction_with_fee(is_fee_private, rng, 0) { Transaction::Execute(_, _, execution, _) => execution, _ => unreachable!(), }; @@ -232,9 +297,9 @@ pub fn sample_fee_private_hardcoded(rng: &mut TestRng) -> Fee { /// Samples a random private fee. pub fn sample_fee_private(deployment_or_execution_id: Field, rng: &mut TestRng) -> Fee { // Sample the genesis block, transaction, and private key. - let (block, transaction, private_key) = crate::sample_genesis_block_and_components(rng); + let (block, transactions, private_key) = crate::sample_genesis_block_and_components(rng); // Retrieve a credits record. - let credits = transaction.records().next().unwrap().1.clone(); + let credits = transactions.iter().next().unwrap().records().next().unwrap().1.clone(); // Decrypt the record. let credits = credits.decrypt(&private_key.try_into().unwrap()).unwrap(); // Sample a base fee in microcredits. @@ -365,6 +430,7 @@ function large_transaction: /// Samples a random deployment transaction with a private or public fee. pub fn sample_deployment_transaction( + version: u8, edition: u16, is_fee_private: bool, rng: &mut TestRng, @@ -372,11 +438,19 @@ pub fn sample_deployment_transaction( // Sample a private key. let private_key = PrivateKey::new(rng).unwrap(); // Sample a deployment. - let deployment = crate::sample_deployment(rng); - // Create a new deployment with the desired edition. - let deployment = - Deployment::::new(edition, deployment.program().clone(), deployment.verifying_keys().clone()) - .unwrap(); + let deployment = match version { + 1 => sample_deployment_v1(edition, rng), + 2 => { + let mut deployment = sample_deployment_v2(edition, rng); + // Set the program checksum. + deployment.set_program_checksum_raw(Some(deployment.program().to_checksum())); + // Set the program owner to the address of the private key. + deployment.set_program_owner_raw(Some(Address::try_from(&private_key).unwrap())); + // Return the deployment. + deployment + } + _ => panic!("Invalid deployment version: {version}"), + }; // Compute the deployment ID. let deployment_id = deployment.to_deployment_id().unwrap(); @@ -394,9 +468,13 @@ pub fn sample_deployment_transaction( } /// Samples a random execution transaction with a private or public fee. -pub fn sample_execution_transaction_with_fee(is_fee_private: bool, rng: &mut TestRng) -> Transaction { +pub fn sample_execution_transaction_with_fee( + is_fee_private: bool, + rng: &mut TestRng, + index: usize, +) -> Transaction { // Sample an execution. - let execution = crate::sample_execution(rng); + let execution = crate::sample_execution(rng, index); // Compute the execution ID. let execution_id = execution.to_execution_id().unwrap(); @@ -500,19 +578,21 @@ pub fn sample_genesis_block(rng: &mut TestRng) -> Block { block } -/// Samples a random genesis block and the transaction from the genesis block. -pub fn sample_genesis_block_and_transaction(rng: &mut TestRng) -> (Block, Transaction) { +/// Samples a random genesis block and the transactions from the genesis block. +pub fn sample_genesis_block_and_transactions( + rng: &mut TestRng, +) -> (Block, Transactions) { // Sample the genesis block and components. - let (block, transaction, _) = crate::sample_genesis_block_and_components(rng); - // Return the block and transaction. - (block, transaction) + let (block, transactions, _) = crate::sample_genesis_block_and_components(rng); + // Return the block and transactions. + (block, transactions) } -/// Samples a random genesis block, the transaction from the genesis block, and the genesis private key. +/// Samples a random genesis block, the transactions from the genesis block, and the genesis private key. pub fn sample_genesis_block_and_components( rng: &mut TestRng, -) -> (Block, Transaction, PrivateKey) { - static INSTANCE: OnceLock<(Block, Transaction, PrivateKey)> = +) -> (Block, Transactions, PrivateKey) { + static INSTANCE: OnceLock<(Block, Transactions, PrivateKey)> = OnceLock::new(); INSTANCE.get_or_init(|| crate::sample_genesis_block_and_components_raw(rng)).clone() } @@ -525,10 +605,10 @@ pub fn sample_genesis_private_key(rng: &mut TestRng) -> PrivateKey (Block, Transaction, PrivateKey) { +) -> (Block, Transactions, PrivateKey) { // Sample the genesis private key. let private_key = sample_genesis_private_key(rng); let address = Address::::try_from(private_key).unwrap(); @@ -542,29 +622,32 @@ fn sample_genesis_block_and_components_raw( // Initialize the process. let process = Process::load().unwrap(); - // Authorize the function. - let authorization = - process.authorize::(&private_key, locator.0, locator.1, inputs.iter(), rng).unwrap(); - // Execute the function. - let (_, mut trace) = process.execute::(authorization, rng).unwrap(); + // Create the transactions. + let transactions = { + Transactions::from_iter((0..2).map(|_| { + // Authorize the function. + let authorization = + process.authorize::(&private_key, locator.0, locator.1, inputs.iter(), rng).unwrap(); + // Execute the function. + let (_, mut trace) = process.execute::(authorization, rng).unwrap(); - // Initialize a new block store. - let block_store = BlockStore::>::open(StorageMode::new_test(None)).unwrap(); + // Initialize a new block store. + let block_store = BlockStore::>::open(StorageMode::new_test(None)).unwrap(); - // Prepare the assignments. - trace.prepare(&Query::from(block_store)).unwrap(); - // Compute the proof and construct the execution. - let execution = trace.prove_execution::(locator.0, VarunaVersion::V1, rng).unwrap(); - // Convert the execution. - // Note: This is a testing-only hack to adhere to Rust's dependency cycle rules. - let execution = Execution::from_str(&execution.to_string()).unwrap(); + // Prepare the assignments. + trace.prepare(&Query::from(block_store)).unwrap(); + // Compute the proof and construct the execution. + let execution = trace.prove_execution::(locator.0, VarunaVersion::V1, rng).unwrap(); + // Convert the execution. + // Note: This is a testing-only hack to adhere to Rust's dependency cycle rules. + let execution = Execution::from_str(&execution.to_string()).unwrap(); - // Construct the transaction. - let transaction = Transaction::from_execution(execution, None).unwrap(); - // Prepare the confirmed transaction. - let confirmed = ConfirmedTransaction::accepted_execute(0, transaction.clone(), vec![]).unwrap(); - // Prepare the transactions. - let transactions = Transactions::from_iter([confirmed]); + // Construct the transaction. + let transaction = Transaction::from_execution(execution, None).unwrap(); + // Prepare the confirmed transaction. + ConfirmedTransaction::accepted_execute(0, transaction.clone(), vec![]).unwrap() + })) + }; // Construct the ratifications. let ratifications = Ratifications::try_from(vec![]).unwrap(); @@ -582,12 +665,12 @@ fn sample_genesis_block_and_components_raw( ratifications, None.into(), vec![], - transactions, + transactions.clone(), vec![], rng, ) .unwrap(); assert!(block.header().is_genesis(), "Failed to initialize a genesis block"); // Return the block, transaction, and private key. - (block, transaction, private_key) + (block, transactions, private_key) } diff --git a/synthesizer/process/benches/check_deployment.rs b/synthesizer/process/benches/check_deployment.rs index 0b1fff6183..6113194b5c 100644 --- a/synthesizer/process/benches/check_deployment.rs +++ b/synthesizer/process/benches/check_deployment.rs @@ -46,14 +46,28 @@ fn prepare_check_deployment>( let program_id = *program.id(); // Retrieve the input types. let input_types = program.get_function(&function_name).unwrap().input_types(); + // Retrieve the 'program_checksum', if the program has a constructor. + let program_checksum = match program.contains_constructor() { + true => Some(stack.program_checksum_as_field().unwrap()), + false => None, + }; // Sample 'root_tvk'. let root_tvk = None; // Sample 'is_root'. let is_root = true; // Compute the request. - let request = - Request::sign(private_key, program_id, function_name, inputs.iter(), &input_types, root_tvk, is_root, rng) - .unwrap(); + let request = Request::sign( + private_key, + program_id, + function_name, + inputs.iter(), + &input_types, + root_tvk, + is_root, + program_checksum, + rng, + ) + .unwrap(); // Initialize the assignments. let assignments = Assignments::::default(); // Initialize the call stack. diff --git a/synthesizer/process/src/cost.rs b/synthesizer/process/src/cost.rs index 449acc7812..872004f070 100644 --- a/synthesizer/process/src/cost.rs +++ b/synthesizer/process/src/cost.rs @@ -13,18 +13,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{Process, Stack}; +use crate::{FinalizeTypes, Process, Stack, StackRef, StackTrait}; -use crate::stack::StackRef; use console::{ prelude::*, program::{FinalizeType, Identifier, LiteralType, PlaintextType}, }; use snarkvm_ledger_block::{Deployment, Execution, Transaction}; -use snarkvm_synthesizer_program::{CastType, Command, Finalize, Instruction, Operand, StackTrait}; +use snarkvm_synthesizer_program::{CastType, Command, Instruction, Operand}; -/// Returns the *minimum* cost in microcredits to publish the given deployment (total cost, (storage cost, synthesis cost, namespace cost)). -pub fn deployment_cost(deployment: &Deployment) -> Result<(u64, (u64, u64, u64))> { +/// Returns the *minimum* cost in microcredits to publish the given deployment (total cost, (storage cost, synthesis cost, constructor cost, namespace cost)). +pub fn deployment_cost( + process: &Process, + deployment: &Deployment, +) -> Result<(u64, (u64, u64, u64, u64))> { // Determine the number of bytes in the deployment. let size_in_bytes = deployment.size_in_bytes()?; // Retrieve the program ID. @@ -44,7 +46,10 @@ pub fn deployment_cost(deployment: &Deployment) -> Result<(u64, ( // Compute the synthesis cost in microcredits. let synthesis_cost = num_combined_variables.saturating_add(num_combined_constraints) * N::SYNTHESIS_FEE_MULTIPLIER; - // Compute the namespace cost in credits: 10^(10 - num_characters). + // Compute the constructor cost in microcredits. + let constructor_cost = constructor_cost_in_microcredits(&Stack::new(process, deployment.program())?)?; + + // Compute the namespace cost in microcredits: 10^(10 - num_characters) * 1e6 let namespace_cost = 10u64 .checked_pow(10u32.saturating_sub(num_characters)) .ok_or(anyhow!("The namespace cost computation overflowed for a deployment"))? @@ -53,10 +58,11 @@ pub fn deployment_cost(deployment: &Deployment) -> Result<(u64, ( // Compute the total cost in microcredits. let total_cost = storage_cost .checked_add(synthesis_cost) + .and_then(|x| x.checked_add(constructor_cost)) .and_then(|x| x.checked_add(namespace_cost)) .ok_or(anyhow!("The total cost computation overflowed for a deployment"))?; - Ok((total_cost, (storage_cost, synthesis_cost, namespace_cost))) + Ok((total_cost, (storage_cost, synthesis_cost, constructor_cost, namespace_cost))) } /// Returns the *minimum* cost in microcredits to publish the given execution (total cost, (storage cost, finalize cost)). @@ -169,27 +175,24 @@ fn plaintext_size_in_bytes(stack: &Stack, plaintext_type: &Plaint /// A helper function to compute the following: base_cost + (byte_multiplier * size_of_operands). fn cost_in_size<'a, N: Network>( stack: &Stack, - finalize: &Finalize, + finalize_types: &FinalizeTypes, operands: impl IntoIterator>, byte_multiplier: u64, base_cost: u64, ) -> Result { - // Retrieve the finalize types. - let finalize_types = stack.get_finalize_types(finalize.name())?; // Compute the size of the operands. let size_of_operands = operands.into_iter().try_fold(0u64, |acc, operand| { // Determine the size of the operand. let operand_size = match finalize_types.get_type_from_operand(stack, operand)? { FinalizeType::Plaintext(plaintext_type) => plaintext_size_in_bytes(stack, &plaintext_type)?, FinalizeType::Future(future) => { - bail!("Future '{future}' is not a valid operand in the finalize scope"); + bail!("Future '{future}' is not a valid operand"); } }; // Safely add the size to the accumulator. acc.checked_add(operand_size).ok_or(anyhow!( - "Overflowed while computing the size of the operand '{operand}' in '{}/{}' (finalize)", + "Overflowed while computing the size of the operand '{operand}' in '{}'", stack.program_id(), - finalize.name() )) })?; // Return the cost. @@ -199,7 +202,7 @@ fn cost_in_size<'a, N: Network>( /// Returns the the cost of a command in a finalize scope. pub fn cost_per_command( stack: &Stack, - finalize: &Finalize, + finalize_types: &FinalizeTypes, command: &Command, consensus_fee_version: ConsensusFeeVersion, ) -> Result { @@ -239,28 +242,26 @@ pub fn cost_per_command( | CastType::ExternalRecord(_) => Ok(500), }, Command::Instruction(Instruction::CommitBHP256(commit)) => { - cost_in_size(stack, finalize, commit.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST) + cost_in_size(stack, finalize_types, commit.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST) } Command::Instruction(Instruction::CommitBHP512(commit)) => { - cost_in_size(stack, finalize, commit.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST) + cost_in_size(stack, finalize_types, commit.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST) } Command::Instruction(Instruction::CommitBHP768(commit)) => { - cost_in_size(stack, finalize, commit.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST) + cost_in_size(stack, finalize_types, commit.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST) } Command::Instruction(Instruction::CommitBHP1024(commit)) => { - cost_in_size(stack, finalize, commit.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST) + cost_in_size(stack, finalize_types, commit.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST) } Command::Instruction(Instruction::CommitPED64(commit)) => { - cost_in_size(stack, finalize, commit.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST) + cost_in_size(stack, finalize_types, commit.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST) } Command::Instruction(Instruction::CommitPED128(commit)) => { - cost_in_size(stack, finalize, commit.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST) + cost_in_size(stack, finalize_types, commit.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST) } Command::Instruction(Instruction::Div(div)) => { // Ensure `div` has exactly two operands. ensure!(div.operands().len() == 2, "'div' must contain exactly 2 operands"); - // Retrieve the finalize types. - let finalize_types = stack.get_finalize_types(finalize.name())?; // Retrieve the price by the operand type. match finalize_types.get_type_from_operand(stack, &div.operands()[0])? { FinalizeType::Plaintext(PlaintextType::Literal(LiteralType::Field)) => Ok(1_500), @@ -275,49 +276,49 @@ pub fn cost_per_command( Command::Instruction(Instruction::GreaterThan(_)) => Ok(500), Command::Instruction(Instruction::GreaterThanOrEqual(_)) => Ok(500), Command::Instruction(Instruction::HashBHP256(hash)) => { - cost_in_size(stack, finalize, hash.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST) + cost_in_size(stack, finalize_types, hash.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST) } Command::Instruction(Instruction::HashBHP512(hash)) => { - cost_in_size(stack, finalize, hash.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST) + cost_in_size(stack, finalize_types, hash.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST) } Command::Instruction(Instruction::HashBHP768(hash)) => { - cost_in_size(stack, finalize, hash.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST) + cost_in_size(stack, finalize_types, hash.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST) } Command::Instruction(Instruction::HashBHP1024(hash)) => { - cost_in_size(stack, finalize, hash.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST) + cost_in_size(stack, finalize_types, hash.operands(), HASH_BHP_PER_BYTE_COST, HASH_BHP_BASE_COST) } Command::Instruction(Instruction::HashKeccak256(hash)) => { - cost_in_size(stack, finalize, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST) + cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST) } Command::Instruction(Instruction::HashKeccak384(hash)) => { - cost_in_size(stack, finalize, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST) + cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST) } Command::Instruction(Instruction::HashKeccak512(hash)) => { - cost_in_size(stack, finalize, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST) + cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST) } Command::Instruction(Instruction::HashPED64(hash)) => { - cost_in_size(stack, finalize, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST) + cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST) } Command::Instruction(Instruction::HashPED128(hash)) => { - cost_in_size(stack, finalize, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST) + cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST) } Command::Instruction(Instruction::HashPSD2(hash)) => { - cost_in_size(stack, finalize, hash.operands(), HASH_PSD_PER_BYTE_COST, HASH_PSD_BASE_COST) + cost_in_size(stack, finalize_types, hash.operands(), HASH_PSD_PER_BYTE_COST, HASH_PSD_BASE_COST) } Command::Instruction(Instruction::HashPSD4(hash)) => { - cost_in_size(stack, finalize, hash.operands(), HASH_PSD_PER_BYTE_COST, HASH_PSD_BASE_COST) + cost_in_size(stack, finalize_types, hash.operands(), HASH_PSD_PER_BYTE_COST, HASH_PSD_BASE_COST) } Command::Instruction(Instruction::HashPSD8(hash)) => { - cost_in_size(stack, finalize, hash.operands(), HASH_PSD_PER_BYTE_COST, HASH_PSD_BASE_COST) + cost_in_size(stack, finalize_types, hash.operands(), HASH_PSD_PER_BYTE_COST, HASH_PSD_BASE_COST) } Command::Instruction(Instruction::HashSha3_256(hash)) => { - cost_in_size(stack, finalize, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST) + cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST) } Command::Instruction(Instruction::HashSha3_384(hash)) => { - cost_in_size(stack, finalize, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST) + cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST) } Command::Instruction(Instruction::HashSha3_512(hash)) => { - cost_in_size(stack, finalize, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST) + cost_in_size(stack, finalize_types, hash.operands(), HASH_PER_BYTE_COST, HASH_BASE_COST) } Command::Instruction(Instruction::HashManyPSD2(_)) => { bail!("`hash_many.psd2` is not supported in finalize") @@ -337,8 +338,6 @@ pub fn cost_per_command( Command::Instruction(Instruction::Mul(mul)) => { // Ensure `mul` has exactly two operands. ensure!(mul.operands().len() == 2, "'mul' must contain exactly 2 operands"); - // Retrieve the finalize types. - let finalize_types = stack.get_finalize_types(finalize.name())?; // Retrieve the price by operand type. match finalize_types.get_type_from_operand(stack, &mul.operands()[0])? { FinalizeType::Plaintext(PlaintextType::Literal(LiteralType::Group)) => Ok(10_000), @@ -358,8 +357,6 @@ pub fn cost_per_command( Command::Instruction(Instruction::Pow(pow)) => { // Ensure `pow` has at least one operand. ensure!(!pow.operands().is_empty(), "'pow' must contain at least 1 operand"); - // Retrieve the finalize types. - let finalize_types = stack.get_finalize_types(finalize.name())?; // Retrieve the price by operand type. match finalize_types.get_type_from_operand(stack, &pow.operands()[0])? { FinalizeType::Plaintext(PlaintextType::Literal(LiteralType::Field)) => Ok(1_500), @@ -373,7 +370,7 @@ pub fn cost_per_command( Command::Instruction(Instruction::Rem(_)) => Ok(500), Command::Instruction(Instruction::RemWrapped(_)) => Ok(500), Command::Instruction(Instruction::SignVerify(sign)) => { - cost_in_size(stack, finalize, sign.operands(), HASH_PSD_PER_BYTE_COST, HASH_PSD_BASE_COST) + cost_in_size(stack, finalize_types, sign.operands(), HASH_PSD_PER_BYTE_COST, HASH_PSD_BASE_COST) } Command::Instruction(Instruction::Shl(_)) => Ok(500), Command::Instruction(Instruction::ShlWrapped(_)) => Ok(500), @@ -387,24 +384,46 @@ pub fn cost_per_command( Command::Instruction(Instruction::Xor(_)) => Ok(500), Command::Await(_) => Ok(500), Command::Contains(command) => { - cost_in_size(stack, finalize, [command.key()], MAPPING_PER_BYTE_COST, mapping_base_cost) + cost_in_size(stack, finalize_types, [command.key()], MAPPING_PER_BYTE_COST, mapping_base_cost) } Command::Get(command) => { - cost_in_size(stack, finalize, [command.key()], MAPPING_PER_BYTE_COST, mapping_base_cost) + cost_in_size(stack, finalize_types, [command.key()], MAPPING_PER_BYTE_COST, mapping_base_cost) } Command::GetOrUse(command) => { - cost_in_size(stack, finalize, [command.key()], MAPPING_PER_BYTE_COST, mapping_base_cost) + cost_in_size(stack, finalize_types, [command.key()], MAPPING_PER_BYTE_COST, mapping_base_cost) } Command::RandChaCha(_) => Ok(25_000), Command::Remove(_) => Ok(SET_BASE_COST), Command::Set(command) => { - cost_in_size(stack, finalize, [command.key(), command.value()], SET_PER_BYTE_COST, SET_BASE_COST) + cost_in_size(stack, finalize_types, [command.key(), command.value()], SET_PER_BYTE_COST, SET_BASE_COST) } Command::BranchEq(_) | Command::BranchNeq(_) => Ok(500), Command::Position(_) => Ok(100), } } +/// Returns the minimum number of microcredits required to run the constructor in the given stack. +/// If a constructor does not exist, no cost is incurred. +pub fn constructor_cost_in_microcredits(stack: &Stack) -> Result { + match stack.program().constructor() { + Some(constructor) => { + // Get the constructor types. + let constructor_types = stack.get_constructor_types()?; + // Get the base cost of the constructor. + let base_cost = constructor + .commands() + .iter() + .map(|command| cost_per_command(stack, &constructor_types, command, ConsensusFeeVersion::V2)) + .try_fold(0u64, |acc, res| { + res.and_then(|x| acc.checked_add(x).ok_or(anyhow!("Constructor cost overflowed"))) + })?; + // Scale by the multiplier. + base_cost.checked_mul(N::CONSTRUCTOR_FEE_MULTIPLIER).ok_or(anyhow!("Constructor cost overflowed")) + } + None => Ok(0), + } +} + /// Returns the minimum number of microcredits required to run the finalize. pub fn cost_in_microcredits_v2(stack: &Stack, function_name: &Identifier) -> Result { cost_in_microcredits(stack, function_name, ConsensusFeeVersion::V2) @@ -441,19 +460,26 @@ fn cost_in_microcredits( // Queue the futures to be tallied. for input in finalize.inputs() { if let FinalizeType::Future(future) = input.finalize_type() { - // Get the external stack for the future. - let external_stack = stack_ref.get_external_stack(future.program_id())?; // Increment the number of finalize blocks seen. num_finalizes += 1; + // If the locator matches the program ID of the provided stack, use it directly. + // Otherwise, retrieve the external stack. + let stack = if future.program_id() == stack.program().id() { + StackRef::Internal(stack) + } else { + StackRef::External(stack_ref.get_external_stack(future.program_id())?) + }; // Queue the future. - finalizes.push((StackRef::External(external_stack), *future.resource())); + finalizes.push((stack, *future.resource())); } } + // Get the finalize types. + let finalize_types = stack_ref.get_finalize_types(finalize.name())?; // Iterate over the commands in the finalize block. for command in finalize.commands() { // Sum the cost of all commands in the current future into the total running cost. finalize_cost = finalize_cost - .checked_add(cost_per_command(&stack_ref, finalize, command, consensus_fee_version)?) + .checked_add(cost_per_command(&stack_ref, &finalize_types, command, consensus_fee_version)?) .ok_or(anyhow!("Finalize cost overflowed"))?; } } @@ -465,8 +491,12 @@ fn cost_in_microcredits( mod tests { use super::*; use crate::test_helpers::get_execution; + use circuit::{Aleo, AleoCanaryV0, AleoTestnetV0, AleoV0}; - use console::network::{CanaryV0, MainnetV0, TestnetV0}; + use console::{ + network::{CanaryV0, MainnetV0, TestnetV0}, + types::Address, + }; use snarkvm_synthesizer_program::Program; // Test program with two functions just below and above the size threshold. @@ -548,4 +578,103 @@ function over_five_thousand: assert_eq!(storage_cost_under_5000, execution_storage_cost::(execution_size_under_5000)); assert_eq!(storage_cost_over_5000, execution_storage_cost::(execution_size_over_5000)); } + + #[test] + fn test_deployment_cost_with_constructors() { + // A helper to run the test. + fn run_test>() { + let process = Process::::load().unwrap(); + let rng = &mut TestRng::default(); + + // Define the programs. + let program_0 = Program::from_str( + r" +program program_with_constructor.aleo; + +constructor: + assert.eq true true; + +mapping foo: + key as field.public; + value as field.public; + +function dummy:", + ) + .unwrap(); + + let program_1 = Program::from_str( + r" +program program_with_constructor.aleo; + +constructor: + assert.eq edition 0u16; + +mapping foo: + key as field.public; + value as field.public; + +function dummy:", + ) + .unwrap(); + + let program_2 = Program::from_str( + r" +program program_with_constructor.aleo; + +constructor: + get foo[0field] into r0; + +mapping foo: + key as field.public; + value as field.public; + +function dummy:", + ) + .unwrap(); + + let program_3 = Program::from_str( + r" +program program_with_constructor.aleo; + +constructor: + set 0field into foo[0field]; + +mapping foo: + key as field.public; + value as field.public; + +function dummy:", + ) + .unwrap(); + + // Verify the deployment costs. + let mut deployment_0 = process.deploy::(&program_0, rng).unwrap(); + deployment_0.set_program_checksum_raw(Some(deployment_0.program().to_checksum())); + deployment_0.set_program_owner_raw(Some(Address::rand(rng))); + assert_eq!(deployment_cost(&process, &deployment_0).unwrap(), (2532500, (879000, 603500, 50000, 1000000))); + + let mut deployment_1 = process.deploy::(&program_1, rng).unwrap(); + deployment_1.set_program_checksum_raw(Some(deployment_1.program().to_checksum())); + deployment_1.set_program_owner_raw(Some(Address::rand(rng))); + assert_eq!(deployment_cost(&process, &deployment_1).unwrap(), (2531500, (878000, 603500, 50000, 1000000))); + + let mut deployment_2 = process.deploy::(&program_2, rng).unwrap(); + deployment_2.set_program_checksum_raw(Some(deployment_2.program().to_checksum())); + deployment_2.set_program_owner_raw(Some(Address::rand(rng))); + assert_eq!(deployment_cost(&process, &deployment_2).unwrap(), (2696500, (911000, 603500, 182000, 1000000))); + + let mut deployment_3 = process.deploy::(&program_3, rng).unwrap(); + deployment_3.set_program_checksum_raw(Some(deployment_3.program().to_checksum())); + deployment_3.set_program_owner_raw(Some(Address::rand(rng))); + assert_eq!( + deployment_cost(&process, &deployment_3).unwrap(), + (4186500, (943000, 603500, 1640000, 1000000)) + ); + } + + // Run the tests for all networks. + run_test::(); + run_test::(); + run_test::(); + } } diff --git a/synthesizer/process/src/deploy.rs b/synthesizer/process/src/deploy.rs index ebc1a1e446..5aee7f74c4 100644 --- a/synthesizer/process/src/deploy.rs +++ b/synthesizer/process/src/deploy.rs @@ -45,9 +45,14 @@ impl Process { let timer = timer!("Process::load_deployment"); // Compute the program stack. - let stack = Stack::new(self, deployment.program())?; + let mut stack = Stack::new(self, deployment.program())?; lap!(timer, "Compute the stack"); + // Set the program owner. + // Note: The program owner is only enforced to be `Some` after `ConsensusVersion::V9` + // and is `None` for all programs deployed before the `V9` migration. + stack.set_program_owner(deployment.program_owner()); + // Insert the verifying keys. for (function_name, (verifying_key, _)) in deployment.verifying_keys() { stack.insert_verifying_key(function_name, verifying_key.clone())?; diff --git a/synthesizer/process/src/execute.rs b/synthesizer/process/src/execute.rs index c80a085826..057f78d9ce 100644 --- a/synthesizer/process/src/execute.rs +++ b/synthesizer/process/src/execute.rs @@ -33,9 +33,9 @@ impl Process { #[cfg(feature = "aleo-cli")] println!("{}", format!(" • Executing '{locator}'...",).dimmed()); - // This is the root request and does not have a caller. + // The root request does not have a caller. let caller = None; - // This is the root request and we do not have a root_tvk to pass on. + // The root request does not have to pass on another request's root_tvk. let root_tvk = None; // Initialize the trace. let trace = Arc::new(RwLock::new(Trace::new())); diff --git a/synthesizer/process/src/finalize.rs b/synthesizer/process/src/finalize.rs index 0f036758c5..eae23caad5 100644 --- a/synthesizer/process/src/finalize.rs +++ b/synthesizer/process/src/finalize.rs @@ -35,9 +35,14 @@ impl Process { let timer = timer!("Process::finalize_deployment"); // Compute the program stack. - let stack = Stack::new(self, deployment.program())?; + let mut stack = Stack::new(self, deployment.program())?; lap!(timer, "Compute the stack"); + // Set the program owner. + // Note: The program owner is only enforced to be `Some` after `ConsensusVersion::V9` + // and is `None` for all programs deployed before the `V9` migration. + stack.set_program_owner(deployment.program_owner()); + // Insert the verifying keys. for (function_name, (verifying_key, _)) in deployment.verifying_keys() { stack.insert_verifying_key(function_name, verifying_key.clone())?; @@ -86,8 +91,17 @@ impl Process { // Initialize the mapping. finalize_operations.push(store.initialize_mapping(*program_id, *mapping.name())?); } - finish!(timer, "Initialize the program mappings"); + lap!(timer, "Initialize the program mappings"); + + // If the program has a constructor, execute it and extend the finalize operations. + // This must happen after the mappings are initialized as the constructor may depend on them. + if deployment.program().contains_constructor() { + let operations = finalize_constructor(state, store, &stack, N::TransitionID::default())?; + finalize_operations.extend(operations); + lap!(timer, "Execute the constructor"); + } + finish!(timer, "Finished finalizing the deployment"); // Return the stack and finalize operations. Ok((stack, finalize_operations)) }) @@ -201,6 +215,64 @@ fn finalize_fee_transition>( } } +/// Finalizes the constructor. +fn finalize_constructor>( + state: FinalizeGlobalState, + store: &FinalizeStore, + stack: &Stack, + transition_id: N::TransitionID, +) -> Result>> { + // Retrieve the program ID. + let program_id = stack.program_id(); + #[cfg(debug_assertions)] + println!("Finalizing constructor for {}...", stack.program_id()); + + // Initialize a list for finalize operations. + let mut finalize_operations = Vec::new(); + + // Initialize a nonce for the constructor registers. + // Currently, this nonce is set to zero for every constructor. + let nonce = 0; + + // Get the constructor logic. If the program does not have a constructor, return early. + let Some(constructor) = stack.program().constructor() else { + return Ok(finalize_operations); + }; + + // Get the constructor types. + let constructor_types = stack.get_constructor_types()?.clone(); + + // Initialize the finalize registers. + let mut registers = FinalizeRegisters::new(state, transition_id, *program_id.name(), constructor_types, nonce); + + // Initialize a counter for the commands. + let mut counter = 0; + + // Evaluate the commands. + while counter < constructor.commands().len() { + // Retrieve the command. + let command = &constructor.commands()[counter]; + // Finalize the command. + match &command { + Command::Await(_) => { + bail!("Cannot `await` a Future in a constructor") + } + _ => finalize_command_except_await( + store, + stack, + &mut registers, + constructor.positions(), + command, + &mut counter, + &mut finalize_operations, + )?, + }; + } + + // Return the finalize operations. + Ok(finalize_operations) +} + /// Finalizes the given transition. fn finalize_transition>( state: FinalizeGlobalState, @@ -250,7 +322,7 @@ fn finalize_transition>( // Get the finalize logic. let Some(finalize) = stack.get_function_ref(registers.function_name())?.finalize_logic() else { bail!( - "The function '{}/{}' does not have an associated finalize block", + "The function '{}/{}' does not have an associated finalize scope", stack.program_id(), registers.function_name() ) @@ -261,32 +333,6 @@ fn finalize_transition>( let command = &finalize.commands()[counter]; // Finalize the command. match &command { - Command::BranchEq(branch_eq) => { - let result = - try_vm_runtime!(|| branch_to(counter, branch_eq, finalize.positions(), &stack, ®isters)); - match result { - Ok(Ok(new_counter)) => { - counter = new_counter; - } - // If the evaluation fails, bail and return the error. - Ok(Err(error)) => bail!("'finalize' failed to evaluate command ({command}): {error}"), - // If the evaluation fails, bail and return the error. - Err(_) => bail!("'finalize' failed to evaluate command ({command})"), - } - } - Command::BranchNeq(branch_neq) => { - let result = - try_vm_runtime!(|| branch_to(counter, branch_neq, finalize.positions(), &stack, ®isters)); - match result { - Ok(Ok(new_counter)) => { - counter = new_counter; - } - // If the evaluation fails, bail and return the error. - Ok(Err(error)) => bail!("'finalize' failed to evaluate command ({command}): {error}"), - // If the evaluation fails, bail and return the error. - Err(_) => bail!("'finalize' failed to evaluate command ({command})"), - } - } Command::Await(await_) => { // Check that the `await` register's is a locator. if let Register::Access(_, _) = await_.register() { @@ -354,20 +400,15 @@ fn finalize_transition>( continue 'outer; } - _ => { - let result = try_vm_runtime!(|| command.finalize(stack.deref(), store, &mut registers)); - match result { - // If the evaluation succeeds with an operation, add it to the list. - Ok(Ok(Some(finalize_operation))) => finalize_operations.push(finalize_operation), - // If the evaluation succeeds with no operation, continue. - Ok(Ok(None)) => {} - // If the evaluation fails, bail and return the error. - Ok(Err(error)) => bail!("'finalize' failed to evaluate command ({command}): {error}"), - // If the evaluation fails, bail and return the error. - Err(_) => bail!("'finalize' failed to evaluate command ({command})"), - } - counter += 1; - } + _ => finalize_command_except_await( + store, + stack.deref(), + &mut registers, + finalize.positions(), + command, + &mut counter, + &mut finalize_operations, + )?, }; } // Check that all future registers have been awaited. @@ -388,7 +429,7 @@ fn finalize_transition>( Ok(finalize_operations) } -// A helper struct to track the execution of a finalize block. +// A helper struct to track the execution of a finalize scope. struct FinalizeState { // A counter for the index of the commands. counter: usize, @@ -416,13 +457,12 @@ fn initialize_finalize_state( false => stack.get_external_stack(future.program_id())?, }; // Get the finalize logic and check that it exists. - let finalize = match stack.get_function_ref(future.function_name())?.finalize_logic() { - Some(finalize) => finalize, - None => bail!( - "The function '{}/{}' does not have an associated finalize block", + let Some(finalize) = stack.get_function_ref(future.function_name())?.finalize_logic() else { + bail!( + "The function '{}/{}' does not have an associated finalize scope", future.program_id(), future.function_name() - ), + ) }; // Initialize the registers. let mut registers = FinalizeRegisters::new( @@ -444,6 +484,64 @@ fn initialize_finalize_state( Ok(FinalizeState { counter: 0, registers, stack, call_counter: 0, awaited: Default::default() }) } +// A helper function to finalize all commands except `await`, updating the finalize operations and the counter. +#[inline] +fn finalize_command_except_await( + store: &FinalizeStore>, + stack: &impl StackTrait, + registers: &mut FinalizeRegisters, + positions: &HashMap, usize>, + command: &Command, + counter: &mut usize, + finalize_operations: &mut Vec>, +) -> Result<()> { + // Finalize the command. + match &command { + Command::BranchEq(branch_eq) => { + let result = try_vm_runtime!(|| branch_to(*counter, branch_eq, positions, stack, registers)); + match result { + Ok(Ok(new_counter)) => { + *counter = new_counter; + } + // If the evaluation fails, bail and return the error. + Ok(Err(error)) => bail!("'constructor' failed to evaluate command ({command}): {error}"), + // If the evaluation fails, bail and return the error. + Err(_) => bail!("'constructor' failed to evaluate command ({command})"), + } + } + Command::BranchNeq(branch_neq) => { + let result = try_vm_runtime!(|| branch_to(*counter, branch_neq, positions, stack, registers)); + match result { + Ok(Ok(new_counter)) => { + *counter = new_counter; + } + // If the evaluation fails, bail and return the error. + Ok(Err(error)) => bail!("'constructor' failed to evaluate command ({command}): {error}"), + // If the evaluation fails, bail and return the error. + Err(_) => bail!("'constructor' failed to evaluate command ({command})"), + } + } + Command::Await(_) => { + bail!("Cannot use `finalize_command_except_await` with an 'await' command") + } + _ => { + let result = try_vm_runtime!(|| command.finalize(stack, store, registers)); + match result { + // If the evaluation succeeds with an operation, add it to the list. + Ok(Ok(Some(finalize_operation))) => finalize_operations.push(finalize_operation), + // If the evaluation succeeds with no operation, continue. + Ok(Ok(None)) => {} + // If the evaluation fails, bail and return the error. + Ok(Err(error)) => bail!("'constructor' failed to evaluate command ({command}): {error}"), + // If the evaluation fails, bail and return the error. + Err(_) => bail!("'constructor' failed to evaluate command ({command})"), + } + *counter += 1; + } + }; + Ok(()) +} + // A helper function that sets up the await operation. #[inline] fn setup_await( @@ -468,7 +566,7 @@ fn branch_to( counter: usize, branch: &Branch, positions: &HashMap, usize>, - stack: &Stack, + stack: &impl StackTrait, registers: &impl RegistersTrait, ) -> Result { // Retrieve the inputs. diff --git a/synthesizer/process/src/lib.rs b/synthesizer/process/src/lib.rs index 6094f7da29..6a95da150a 100644 --- a/synthesizer/process/src/lib.rs +++ b/synthesizer/process/src/lib.rs @@ -62,6 +62,7 @@ use snarkvm_synthesizer_program::{ StackTrait, }; use snarkvm_synthesizer_snark::{ProvingKey, UniversalSRS, VerifyingKey}; +use snarkvm_utilities::defer; use aleo_std::prelude::{finish, lap, timer}; use indexmap::IndexMap; @@ -118,23 +119,9 @@ impl Process { Ok(process) } - /// Adds a new program to the process. - /// If the program exists, then the existing program is replaced and discarded. - /// If you intend to `execute` the program, use `deploy` and `finalize_deployment` instead. - #[inline] - pub fn add_program(&mut self, program: &Program) -> Result>>> { - // Initialize the 'credits.aleo' program ID. - let credits_program_id = ProgramID::::from_str("credits.aleo")?; - // If the program is not 'credits.aleo', compute the program stack, and add it to the process. - if program.id() != &credits_program_id { - return Ok(self.add_stack(Stack::new(self, program)?)); - } - Ok(None) - } - /// Adds a new stack to the process. - /// If the program exists, then the existing stack is replaced and discarded - /// If you intend to `execute` the program, use `deploy` and `finalize_deployment` instead. + /// If the program already exists, then the existing stack is replaced and the original stack is returned. + /// Note. This method assumes that the provided stack is valid. #[inline] pub fn add_stack(&mut self, stack: Stack) -> Option>> { // Get the program ID. @@ -291,6 +278,66 @@ impl Process { Ok(process) } + /// Adds a new program to the process, verifying that it is a valid addition. + /// If the program exists, then the existing stack is replaced and discarded. + /// Note. This method should **NOT** be used by the on-chain VM to add new program, use `finalize_deployment` or `load_deployment` instead instead. + #[inline] + pub fn add_program(&mut self, program: &Program) -> Result<()> { + // Initialize the 'credits.aleo' program ID. + let credits_program_id = ProgramID::::from_str("credits.aleo")?; + // If the program is not 'credits.aleo', compute the program stack, and add it to the process. + if program.id() != &credits_program_id { + self.add_stack(Stack::new(self, program)?); + } + Ok(()) + } + + /// Adds a new program with the given edition to the process, verifying that it is a valid addition. + /// If the program exists, then the existing stack is replaced and discarded. + /// Note. This method should **NOT** be used by the on-chain VM to add new program, use `finalize_deployment` or `load_deployment` instead instead. + #[inline] + pub fn add_program_with_edition(&mut self, program: &Program, edition: u16) -> Result<()> { + // Initialize the 'credits.aleo' program ID. + let credits_program_id = ProgramID::::from_str("credits.aleo")?; + // If the program is not 'credits.aleo', compute the program stack, and add it to the process. + if program.id() != &credits_program_id { + let stack = Stack::new_raw(self, program, edition)?; + stack.initialize_and_check(self)?; + self.add_stack(stack); + } + Ok(()) + } + + /// Adds a set of programs and editions, in topological order, to the process, deferring validation of the programs until all programs are added. + /// If a program exists, then the existing stack is replaced and discarded. + /// Either all programs are added or none are. + /// Note. This method should **NOT** be used by the on-chain VM to add new program, use `finalize_deployment` or `load_deployment` instead instead. + #[inline] + pub fn add_programs_with_editions(&mut self, programs: &[(Program, u16)]) -> Result<()> { + // Initialize the 'credits.aleo' program ID. + let credits_program_id = ProgramID::::from_str("credits.aleo")?; + // Defer cleanup of the uncommitted stacks. + defer! { + self.revert_stacks() + } + // Initialize raw stacks for each of the programs, skipping `credits.aleo`. + for (program, edition) in programs { + if program.id() != &credits_program_id { + self.stage_stack(Stack::new_raw(self, program, *edition)?) + } + } + // For each stack, check and initialize it before adding it to the process. + for (program, _) in programs { + // Retrieve the stack. + let stack = self.get_stack(program.id())?; + // Initialize and check the stack for well-formedness. + stack.initialize_and_check(self)?; + } + // Commit the staged stacks. + self.commit_stacks(); + Ok(()) + } + /// Returns the universal SRS. #[inline] pub const fn universal_srs(&self) -> &UniversalSRS { @@ -303,6 +350,12 @@ impl Process { self.stacks.read().contains_key(program_id) } + /// Returns the program IDs of all programs in the process. + #[inline] + pub fn program_ids(&self) -> Vec> { + self.stacks.read().keys().copied().collect() + } + /// Returns the stack for the given program ID. #[inline] pub fn get_stack(&self, program_id: impl TryInto>) -> Result>> { diff --git a/synthesizer/process/src/stack/authorization/mod.rs b/synthesizer/process/src/stack/authorization/mod.rs index 00f408acfd..0d66a7fdbb 100644 --- a/synthesizer/process/src/stack/authorization/mod.rs +++ b/synthesizer/process/src/stack/authorization/mod.rs @@ -157,15 +157,17 @@ impl Authorization { for program_id in program_ids { // There is only one credits.aleo edition, so we can safely skip this case. if program_id.to_string() != "credits.aleo" { - // Get the program's current edition. - let _program_edition = *process.get_stack(program_id)?.program_edition(); - // If we're past ConsensusVersion::V8, ensure new stacks are not on edition 0. - // TODO: Once upgradability lands, this check will be different. We - // can't just check the program edition anymore, it will need to be - // programs that are edition 0 with no constructor that can't be - // called. + // Get the stack. + let stack = process.get_stack(program_id)?; + // Get the program edition. + let _program_edition = *stack.program_edition(); + // If we're past `ConsensusVersion::V8` but before `ConsensusVersion::V9`, ensure new stacks are not on edition 0. + // Note. This check does not apply to programs with constructors. #[cfg(not(any(test, feature = "test")))] - if _consensus_version >= ConsensusVersion::V8 && _program_edition == 0 { + if _consensus_version >= ConsensusVersion::V8 + && _program_edition == 0 + && !stack.program().contains_constructor() + { bail!("Cannot execute {program_id} on edition {_program_edition}"); } } diff --git a/synthesizer/process/src/stack/authorize.rs b/synthesizer/process/src/stack/authorize.rs index f4a3123277..e34248e424 100644 --- a/synthesizer/process/src/stack/authorize.rs +++ b/synthesizer/process/src/stack/authorize.rs @@ -36,14 +36,28 @@ impl Stack { lap!(timer, "Retrieve the input types"); // Set is_root to true. let is_root = true; + // Retrieve the program checksum, if the program has a constructor. + let program_checksum = match self.program().contains_constructor() { + true => Some(self.program_checksum_as_field()?), + false => None, + }; // This is the root request and does not have a caller. let caller = None; // This is the root request and we do not have a root_tvk to pass on. let root_tvk = None; // Compute the request. - let request = - Request::sign(private_key, program_id, function_name, inputs, &input_types, root_tvk, is_root, rng)?; + let request = Request::sign( + private_key, + program_id, + function_name, + inputs, + &input_types, + root_tvk, + is_root, + program_checksum, + rng, + )?; lap!(timer, "Compute the request"); // Initialize the authorization. let authorization = Authorization::new(request.clone()); @@ -83,9 +97,23 @@ impl Stack { let caller = None; // This is the root request and we do not have a root_tvk to pass on. let root_tvk = None; + // Retrieve the program checksum, if the program has a constructor. + let program_checksum = match self.program().contains_constructor() { + true => Some(self.program_checksum_as_field()?), + false => None, + }; // Compute the request. - let request = - Request::sign(private_key, program_id, function_name, inputs, &input_types, root_tvk, is_root, rng)?; + let request = Request::sign( + private_key, + program_id, + function_name, + inputs, + &input_types, + root_tvk, + is_root, + program_checksum, + rng, + )?; lap!(timer, "Compute the request"); // Initialize the authorization. let authorization = Authorization::new(request.clone()); diff --git a/synthesizer/process/src/stack/call/mod.rs b/synthesizer/process/src/stack/call/mod.rs index 81ec4ac674..4fbdec4b61 100644 --- a/synthesizer/process/src/stack/call/mod.rs +++ b/synthesizer/process/src/stack/call/mod.rs @@ -118,6 +118,11 @@ impl CallTrait for Call { if let CallStack::Authorize(requests, private_key, authorization) = &mut call_stack { // Set 'is_root'. let is_root = false; + // Retrieve the program checksum, if the program has a constructor. + let program_checksum = match substack.program().contains_constructor() { + true => Some(substack.program_checksum_as_field()?), + false => None, + }; // Compute the request. let request = Request::sign( private_key, @@ -127,6 +132,7 @@ impl CallTrait for Call { &function.input_types(), root_tvk, is_root, + program_checksum, rng, )?; // Add the request to the requests. @@ -209,6 +215,12 @@ impl CallTrait for Call { // If we are not handling the root request, retrieve the root request's tvk let root_tvk = registers.root_tvk().ok(); + // Retrieve the program checksum, if the program has a constructor. + let program_checksum = match substack.program().contains_constructor() { + true => Some(substack.program_checksum_as_field()?), + false => None, + }; + // If the operator is a closure, retrieve the closure and compute the output. let outputs = if let Ok(closure) = substack.program().get_closure(resource) { lap!(timer, "Execute the closure"); @@ -262,6 +274,7 @@ impl CallTrait for Call { &function.input_types(), root_tvk, is_root, + program_checksum, rng, )?; @@ -290,6 +303,7 @@ impl CallTrait for Call { &function.input_types(), root_tvk, is_root, + program_checksum, rng, )?; @@ -306,7 +320,7 @@ impl CallTrait for Call { (request, response) } // In Synthesize mode (with an existing proving key) or CheckDeployment mode, we generate dummy outputs to avoid building a full sub-circuit. - CallStack::Synthesize(_, private_key, _) | CallStack::CheckDeployment(_, private_key, ..) => { + CallStack::Synthesize(_, private_key, ..) | CallStack::CheckDeployment(_, private_key, ..) => { // Compute the request. let request = Request::sign( private_key, @@ -316,6 +330,7 @@ impl CallTrait for Call { &function.input_types(), root_tvk, is_root, + program_checksum, rng, )?; @@ -384,6 +399,7 @@ impl CallTrait for Call { &function.input_types(), root_tvk, is_root, + program_checksum, rng, )?; diff --git a/synthesizer/process/src/stack/deploy.rs b/synthesizer/process/src/stack/deploy.rs index 874903add2..009dad58f0 100644 --- a/synthesizer/process/src/stack/deploy.rs +++ b/synthesizer/process/src/stack/deploy.rs @@ -51,7 +51,7 @@ impl Stack { finish!(timer); // Return the deployment. - Deployment::new(*self.program_edition(), self.program.clone(), verifying_keys) + Deployment::new(*self.program_edition, self.program.clone(), verifying_keys, None, None) } /// Checks each function in the program on the given verifying key and certificate. @@ -68,16 +68,19 @@ impl Stack { // Ensure the deployment is ordered. deployment.check_is_ordered()?; - // Ensure that edition in the stack and deployment matches. - ensure!( - *self.program_edition == deployment.edition(), - "The stack edition does not match the deployment edition" - ); // Ensure the program in the stack and deployment matches. ensure!(&self.program == deployment.program(), "The stack program does not match the deployment program"); + // If the deployment contains a checksum, ensure it matches the one computed by the stack. + if let Some(program_checksum) = deployment.program_checksum() { + ensure!( + program_checksum == self.program_checksum, + "The deployment checksum does not match the stack checksum" + ); + } // Check Verifying Keys // + // Get the program ID. let program_id = self.program.id(); // Check that the number of combined variables does not exceed the deployment limit. @@ -88,10 +91,9 @@ impl Stack { // Construct the call stacks and assignments used to verify the certificates. let mut call_stacks = Vec::with_capacity(deployment.verifying_keys().len()); - // The `root_tvk` is `None` when verifying the deployment of an individual circuit. + // Sample a dummy `root_tvk` for circuit synthesis. let root_tvk = None; - - // The `caller` is `None` when verifying the deployment of an individual circuit. + // Sample a dummy `caller` for circuit synthesis. let caller = None; // Check that the number of functions matches the number of verifying keys. @@ -123,6 +125,11 @@ impl Stack { let burner_address = Address::try_from(&burner_private_key)?; // Retrieve the input types. let input_types = function.input_types(); + // Retrieve the program checksum, if the program has a constructor. + let program_checksum = match self.program().contains_constructor() { + true => Some(self.program_checksum_as_field()?), + false => None, + }; // Sample the inputs. let inputs = input_types .iter() @@ -137,7 +144,7 @@ impl Stack { }) .collect::>>()?; lap!(timer, "Sample the inputs"); - // Sample 'is_root'. + // Sample a dummy 'is_root'. let is_root = true; // Compute the request, with a burner private key. @@ -149,6 +156,7 @@ impl Stack { &input_types, root_tvk, is_root, + program_checksum, rng, )?; lap!(timer, "Compute the request for {}", function.name()); diff --git a/synthesizer/process/src/stack/evaluate.rs b/synthesizer/process/src/stack/evaluate.rs index 46a9221668..7f47391c98 100644 --- a/synthesizer/process/src/stack/evaluate.rs +++ b/synthesizer/process/src/stack/evaluate.rs @@ -37,7 +37,8 @@ impl Stack { } // Initialize the registers. - let mut registers = Registers::::new(call_stack, self.get_register_types(closure.name())?.clone()); + let mut registers = + Registers::::new(call_stack.clone(), self.get_register_types(closure.name())?.clone()); // Set the transition signer. registers.set_signer(signer); // Set the transition caller. @@ -84,6 +85,12 @@ impl Stack { 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."), + // If the operand is the program checksum, throw an error. + Operand::Checksum(_) => bail!("Cannot retrieve the program checksum from a closure scope."), + // If the operand is the program edition, throw an error. + Operand::Edition(_) => bail!("Cannot retrieve the edition from a closure scope."), + // If the operand is the program owner, throw an error. + Operand::ProgramOwner(_) => bail!("Cannot retrieve the program owner from a closure scope."), } }) .collect(); @@ -145,6 +152,11 @@ impl Stack { None => (true, signer), }; let tvk = *request.tvk(); + // Retrieve the program checksum, if the program has a constructor. + let program_checksum = match self.program().contains_constructor() { + true => Some(self.program_checksum_as_field()?), + false => None, + }; // Ensure the number of inputs matches. if function.inputs().len() != inputs.len() { @@ -175,7 +187,7 @@ impl Stack { lap!(timer, "Initialize the registers"); // Ensure the request is well-formed. - ensure!(request.verify(&function.input_types(), is_root), "[Evaluate] Request is invalid"); + ensure!(request.verify(&function.input_types(), is_root, program_checksum), "[Evaluate] Request is invalid"); lap!(timer, "Verify the request"); // Store the inputs. @@ -227,6 +239,12 @@ impl Stack { 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."), + // If the operand is the program checksum, throw an error. + Operand::Checksum(_) => bail!("Cannot retrieve the program checksum from a function scope."), + // If the operand is the program edition, throw an error. + Operand::Edition(_) => bail!("Cannot retrieve the edition from a function scope."), + // If the operand is the program owner, throw an error. + Operand::ProgramOwner(_) => bail!("Cannot retrieve the program owner from a function scope."), } }) .collect::>>()?; diff --git a/synthesizer/process/src/stack/execute.rs b/synthesizer/process/src/stack/execute.rs index de61f6d6e5..ccafc7b789 100644 --- a/synthesizer/process/src/stack/execute.rs +++ b/synthesizer/process/src/stack/execute.rs @@ -119,6 +119,14 @@ impl Stack { Operand::NetworkID => { bail!("Illegal operation: cannot retrieve the network id in a closure scope") } + // If the operand is the checksum, throw an error. + Operand::Checksum(_) => bail!("Illegal operation: cannot retrieve the checksum in a closure scope"), + // If the operand is the edition, throw an error. + Operand::Edition(_) => bail!("Illegal operation: cannot retrieve the edition in a closure scope"), + // If the operand is the program owner, throw an error. + Operand::ProgramOwner(_) => { + bail!("Illegal operation: cannot retrieve the program owner in a closure scope") + } } }) .collect(); @@ -138,7 +146,7 @@ impl Stack { &self, mut call_stack: CallStack, console_caller: Option>, - root_tvk: Option>, + console_root_tvk: Option>, rng: &mut R, ) -> Result> { let timer = timer!("Stack::execute_function"); @@ -167,7 +175,7 @@ impl Stack { ); // We can only have a root_tvk if this request was called by another request - ensure!(console_caller.is_some() == root_tvk.is_some()); + ensure!(console_caller.is_some() == console_root_tvk.is_some()); // Determine if this is the top-level caller. let console_is_root = console_caller.is_none(); @@ -202,24 +210,33 @@ impl Stack { })?; lap!(timer, "Verify the input types"); + // Retrieve the program checksum, if the program has a constructor. + let program_checksum = match self.program().contains_constructor() { + true => Some(self.program_checksum_as_field()?), + false => None, + }; + // Ensure the request is well-formed. - ensure!(console_request.verify(&input_types, console_is_root), "Request is invalid"); + ensure!( + console_request.verify(&input_types, console_is_root, program_checksum), + "[Execute] Request is invalid" + ); lap!(timer, "Verify the console request"); // Initialize the registers. let mut registers = Registers::new(call_stack, self.get_register_types(function.name())?.clone()); // Set the root tvk, from a parent request or the current request. - // inject the `root_tvk` as `Mode::Private`. - if let Some(root_tvk) = root_tvk { - registers.set_root_tvk(root_tvk); - registers.set_root_tvk_circuit(circuit::Field::::new(circuit::Mode::Private, root_tvk)); - } else { - registers.set_root_tvk(*console_request.tvk()); - registers.set_root_tvk_circuit(circuit::Field::::new(circuit::Mode::Private, *console_request.tvk())); - } + let console_root_tvk = console_root_tvk.unwrap_or(*console_request.tvk()); + // Inject the `root_tvk` as `Mode::Private`. + let root_tvk = circuit::Field::::new(circuit::Mode::Private, console_root_tvk); + // Set the root tvk. + registers.set_root_tvk(console_root_tvk); + // Set the root tvk, as a circuit. + registers.set_root_tvk_circuit(root_tvk.clone()); - let root_tvk = Some(registers.root_tvk_circuit()?); + // If a program checksum was passed in, Inject it as `Mode::Public`. + let program_checksum = program_checksum.map(|c| circuit::Field::::new(circuit::Mode::Public, c)); use circuit::{Eject, Inject}; @@ -236,7 +253,7 @@ impl Stack { let caller = Ternary::ternary(&is_root, request.signer(), &parent); // Ensure the request has a valid signature, inputs, and transition view key. - A::assert(request.verify(&input_types, &tpk, root_tvk, is_root)); + A::assert(request.verify(&input_types, &tpk, Some(root_tvk), is_root, program_checksum)); lap!(timer, "Verify the circuit request"); // Set the transition signer. @@ -352,6 +369,18 @@ impl Stack { Operand::NetworkID => { bail!("Illegal operation: cannot retrieve the network id in a function scope") } + // If the operand is the checksum, throw an error. + Operand::Checksum(_) => { + bail!("Illegal operation: cannot retrieve the checksum in a function scope") + } + // If the operand is the edition, throw an error. + Operand::Edition(_) => { + bail!("Illegal operation: cannot retrieve the edition in a function scope") + } + // If the operand is the program owner, throw an error. + Operand::ProgramOwner(_) => { + bail!("Illegal operation: cannot retrieve the program owner in a function scope") + } } }) .collect::>>()?; diff --git a/synthesizer/process/src/stack/finalize_registers/registers_trait.rs b/synthesizer/process/src/stack/finalize_registers/registers_trait.rs index d3f9eba81c..1917345d91 100644 --- a/synthesizer/process/src/stack/finalize_registers/registers_trait.rs +++ b/synthesizer/process/src/stack/finalize_registers/registers_trait.rs @@ -45,6 +45,36 @@ impl RegistersTrait for FinalizeRegisters { Operand::NetworkID => { return Ok(Value::Plaintext(Plaintext::from(Literal::U16(U16::new(N::ID))))); } + // If the operand is the checksum, load the checksum. + Operand::Checksum(program_id) => { + let checksum = match program_id { + Some(program_id) => *stack.get_external_stack(program_id)?.program_checksum(), + None => *stack.program_checksum(), + }; + return Ok(Value::Plaintext(Plaintext::from(checksum))); + } + // If the operand is the edition, load the edition. + Operand::Edition(program_id) => { + let edition = match program_id { + Some(program_id) => stack.get_external_stack(program_id)?.program_edition(), + None => stack.program_edition(), + }; + return Ok(Value::Plaintext(Plaintext::from(Literal::U16(edition)))); + } + // If the operand is the program owner, load the program address. + Operand::ProgramOwner(program_id) => { + // Get the program owner from the stack. + let program_owner = match program_id { + Some(program_id) => *stack.get_external_stack(program_id)?.program_owner(), + None => *stack.program_owner(), + }; + // Get the address, if it exists. + let address = match program_owner { + Some(address) => address, + None => bail!("The program owner does not exist for the program '{}'.", stack.program_id()), + }; + return Ok(Value::Plaintext(Plaintext::from(Literal::Address(address)))); + } }; // Retrieve the value. diff --git a/synthesizer/process/src/stack/finalize_types/initialize.rs b/synthesizer/process/src/stack/finalize_types/initialize.rs index cc3b8bb1bd..90c11a8ca1 100644 --- a/synthesizer/process/src/stack/finalize_types/initialize.rs +++ b/synthesizer/process/src/stack/finalize_types/initialize.rs @@ -16,6 +16,31 @@ use super::*; impl FinalizeTypes { + /// Initializes a new instance of `FinalizeTypes` for the given constructor. + /// Checks that the given constructor is well-formed for the given stack. + #[inline] + pub(super) fn initialize_finalize_types_from_constructor( + stack: &Stack, + constructor: &Constructor, + ) -> Result { + // Initialize a map of registers to their types. + let mut finalize_types = Self { inputs: IndexMap::new(), destinations: IndexMap::new() }; + + // Check the commands are well-formed. + for command in constructor.commands() { + // Ensure the command is not a call instruction. + ensure!(!command.is_call(), "`call` commands are not allowed in constructors."); + // Ensure the command is not a cast to record instruction. + ensure!(!command.is_cast_to_record(), "`cast` (to record) commands are not allowed in constructors."); + // Ensure the command is not an await command. + ensure!(!command.is_await(), "`await` commands are not allowed in constructors."); + // Check the command opcode, operands, and destinations. + finalize_types.check_command(stack, constructor.positions(), command)?; + } + + Ok(finalize_types) + } + /// Initializes a new instance of `FinalizeTypes` for the given finalize. /// Checks that the given finalize is well-formed for the given stack. /// @@ -25,7 +50,7 @@ impl FinalizeTypes { /// whose finalize is not well-formed, but it is not possible to execute a program whose finalize /// is not well-formed. #[inline] - pub(super) fn initialize_finalize_types(stack: &Stack, finalize: &Finalize) -> Result { + pub(super) fn initialize_finalize_types_from_finalize(stack: &Stack, finalize: &Finalize) -> Result { // Initialize a map of registers to their types. let mut finalize_types = Self { inputs: IndexMap::new(), destinations: IndexMap::new() }; @@ -49,7 +74,7 @@ impl FinalizeTypes { // Step 2. Check the commands are well-formed. Make sure all the input futures are awaited. for command in finalize.commands() { // Check the command opcode, operands, and destinations. - finalize_types.check_command(stack, finalize, command)?; + finalize_types.check_command(stack, finalize.positions(), command)?; // If the command is an `await`, add the future to the set of consumed futures. if let Command::Await(await_) = command { @@ -136,7 +161,13 @@ impl FinalizeTypes { RegisterTypes::check_struct(stack, struct_name)? } FinalizeType::Plaintext(PlaintextType::Array(array_type)) => RegisterTypes::check_array(stack, array_type)?, - FinalizeType::Future(..) => (), + FinalizeType::Future(locator) => { + ensure!( + stack.program().contains_import(locator.program_id()), + "Program '{locator}' is not imported by '{}'.", + stack.program().id() + ) + } }; // Insert the input register. @@ -152,19 +183,39 @@ impl FinalizeTypes { /// Ensures the given command is well-formed. #[inline] - fn check_command(&mut self, stack: &Stack, finalize: &Finalize, command: &Command) -> Result<()> { + fn check_command( + &mut self, + stack: &Stack, + positions: &HashMap, usize>, + command: &Command, + ) -> Result<()> { + // Check the operands. + for operand in command.operands() { + // If the operand is `Operand::Checksum`, `Operand::Edition`, or `Operand::ProgramOwner` and it contains a program ID, + // ensure that the program ID is imported by the current program. + match operand { + Operand::Checksum(program_id) | Operand::Edition(program_id) | Operand::ProgramOwner(program_id) => { + if let Some(program_id) = program_id { + if stack.get_external_stack(program_id).is_err() { + bail!("External program '{program_id}' is not imported by '{}'.", stack.program_id()); + } + } + } + _ => {} + } + } match command { - Command::Instruction(instruction) => self.check_instruction(stack, finalize.name(), instruction)?, + Command::Instruction(instruction) => self.check_instruction(stack, instruction)?, Command::Await(await_) => self.check_await(stack, await_)?, Command::Contains(contains) => self.check_contains(stack, contains)?, Command::Get(get) => self.check_get(stack, get)?, Command::GetOrUse(get_or_use) => self.check_get_or_use(stack, get_or_use)?, - Command::RandChaCha(rand_chacha) => self.check_rand_chacha(stack, finalize.name(), rand_chacha)?, - Command::Remove(remove) => self.check_remove(stack, finalize.name(), remove)?, - Command::Set(set) => self.check_set(stack, finalize.name(), set)?, - Command::BranchEq(branch_eq) => self.check_branch(stack, finalize, branch_eq)?, - Command::BranchNeq(branch_neq) => self.check_branch(stack, finalize, branch_neq)?, - // Note that the `Position`s are checked for uniqueness when constructing `Finalize`. + Command::RandChaCha(rand_chacha) => self.check_rand_chacha(stack, rand_chacha)?, + Command::Remove(remove) => self.check_remove(stack, remove)?, + Command::Set(set) => self.check_set(stack, set)?, + Command::BranchEq(branch_eq) => self.check_branch(stack, positions, branch_eq)?, + Command::BranchNeq(branch_neq) => self.check_branch(stack, positions, branch_neq)?, + // Note that the `Position`s are checked for uniqueness when constructing `Finalize` or `Constructor`. Command::Position(_) => (), } Ok(()) @@ -194,7 +245,7 @@ impl FinalizeTypes { fn check_branch( &mut self, stack: &Stack, - finalize: &Finalize, + positions: &HashMap, usize>, branch: &Branch, ) -> Result<()> { // Get the type of the first operand. @@ -221,7 +272,7 @@ impl FinalizeTypes { ); // Check that the `Position` has been defined. ensure!( - finalize.positions().get(branch.position()).is_some(), + positions.get(branch.position()).is_some(), "Command '{}' expects a defined position to jump to. Found undefined position '{}'", Branch::::opcode(), branch.position() @@ -435,12 +486,7 @@ impl FinalizeTypes { /// Ensure the given `rand.chacha` command is well-formed. #[inline] - fn check_rand_chacha( - &mut self, - _stack: &Stack, - _finalize_name: &Identifier, - rand_chacha: &RandChaCha, - ) -> Result<()> { + fn check_rand_chacha(&mut self, _stack: &Stack, rand_chacha: &RandChaCha) -> Result<()> { // Ensure the number of operands is within bounds. if rand_chacha.operands().len() > MAX_ADDITIONAL_SEEDS { bail!("The number of operands must be <= {MAX_ADDITIONAL_SEEDS}") @@ -466,10 +512,10 @@ impl FinalizeTypes { /// Ensures the given `set` command is well-formed. #[inline] - fn check_set(&self, stack: &Stack, finalize_name: &Identifier, set: &Set) -> Result<()> { + fn check_set(&self, stack: &Stack, set: &Set) -> Result<()> { // Ensure the declared mapping in `set` is defined in the program. if !stack.program().contains_mapping(set.mapping_name()) { - bail!("Mapping '{}' in '{}/{finalize_name}' is not defined.", set.mapping_name(), stack.program_id()) + bail!("Mapping '{}' in '{}' is not defined.", set.mapping_name(), stack.program_id()) } // Retrieve the mapping from the program. // Note that the unwrap is safe, as we have already checked the mapping exists. @@ -507,10 +553,10 @@ impl FinalizeTypes { /// Ensures the given `remove` command is well-formed. #[inline] - fn check_remove(&self, stack: &Stack, finalize_name: &Identifier, remove: &Remove) -> Result<()> { + fn check_remove(&self, stack: &Stack, remove: &Remove) -> Result<()> { // Ensure the declared mapping in `remove` is defined in the program. if !stack.program().contains_mapping(remove.mapping_name()) { - bail!("Mapping '{}' in '{}/{finalize_name}' is not defined.", remove.mapping_name(), stack.program_id()) + bail!("Mapping '{}' in '{}' is not defined.", remove.mapping_name(), stack.program_id()) } // Retrieve the mapping from the program. // Note that the unwrap is safe, as we have already checked the mapping exists. @@ -533,14 +579,9 @@ impl FinalizeTypes { /// Ensures the given instruction is well-formed. #[inline] - fn check_instruction( - &mut self, - stack: &Stack, - finalize_name: &Identifier, - instruction: &Instruction, - ) -> Result<()> { + fn check_instruction(&mut self, stack: &Stack, instruction: &Instruction) -> Result<()> { // Ensure the opcode is well-formed. - self.check_instruction_opcode(stack, finalize_name, instruction)?; + self.check_instruction_opcode(stack, instruction)?; // Initialize a vector to store the register types of the operands. let mut operand_types = Vec::with_capacity(instruction.operands().len()); @@ -574,12 +615,7 @@ impl FinalizeTypes { /// 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( - &mut self, - stack: &Stack, - finalize_name: &Identifier, - instruction: &Instruction, - ) -> Result<()> { + fn check_instruction_opcode(&mut self, stack: &Stack, instruction: &Instruction) -> Result<()> { match instruction.opcode() { Opcode::Literal(opcode) => { // Ensure the opcode **is** a reserved opcode. @@ -604,10 +640,10 @@ impl FinalizeTypes { _ => bail!("Instruction '{instruction}' is not for opcode '{opcode}'."), }, Opcode::Async => { - bail!("Instruction 'async' is not allowed in 'finalize'"); + bail!("Instruction 'async' is not allowed in 'finalize' or 'constructor'."); } Opcode::Call => { - bail!("Instruction 'call' is not allowed in 'finalize'"); + bail!("Instruction 'call' is not allowed in 'finalize' or 'constructor'."); } Opcode::Cast(opcode) => match opcode { "cast" => { @@ -678,7 +714,7 @@ impl FinalizeTypes { _ => bail!("Instruction '{instruction}' is not for opcode '{opcode}'."), }, Opcode::Command(opcode) => { - bail!("Fatal error: Cannot check command '{opcode}' as an instruction in 'finalize {finalize_name}'.") + bail!("Fatal error: Cannot check command '{opcode}' as an instruction.") } Opcode::Commit(opcode) => RegisterTypes::check_commit_opcode(opcode, instruction)?, Opcode::Hash(opcode) => RegisterTypes::check_hash_opcode(opcode, instruction)?, diff --git a/synthesizer/process/src/stack/finalize_types/matches.rs b/synthesizer/process/src/stack/finalize_types/matches.rs index f3dfc76f0b..67bdecf067 100644 --- a/synthesizer/process/src/stack/finalize_types/matches.rs +++ b/synthesizer/process/src/stack/finalize_types/matches.rs @@ -64,11 +64,20 @@ impl FinalizeTypes { "Struct member '{struct_name}.{member_name}' expects {member_type}, but found '{plaintext_type}' in the operand '{operand}'.", ) } - // Ensure the program ID type (address) matches the member type. - Operand::ProgramID(..) => { - // Retrieve the program ID type. - let program_ref_type = PlaintextType::Literal(LiteralType::Address); - // Ensure the program ID type matches the member type. + // Ensure the program ID, block height, network ID, checksum, edition, and program owner types matches the member type. + Operand::ProgramID(..) + | Operand::BlockHeight + | Operand::NetworkID + | Operand::Checksum(_) + | Operand::Edition(_) + | Operand::ProgramOwner(_) => { + // Retrieve the operand type. + let FinalizeType::Plaintext(program_ref_type) = self.get_type_from_operand(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!( &program_ref_type == member_type, "Struct member '{struct_name}.{member_name}' expects {member_type}, but found '{program_ref_type}' in the operand '{operand}'.", @@ -82,26 +91,6 @@ impl FinalizeTypes { Operand::Caller => bail!( "Struct member '{struct_name}.{member_name}' cannot be cast from a caller in a finalize scope." ), - // Ensure the block height type (u32) matches the member type. - Operand::BlockHeight => { - // Retrieve the block height type. - let block_height_type = PlaintextType::Literal(LiteralType::U32); - // Ensure the block height type matches the member type. - ensure!( - &block_height_type == member_type, - "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(()) @@ -128,7 +117,7 @@ impl FinalizeTypes { // Ensure the operand types match the element type. for operand in operands.iter() { match operand { - // Ensure the literal type matches the member type. + // Ensure the literal type matches the element type. Operand::Literal(literal) => { ensure!( &PlaintextType::Literal(literal.to_type()) == array_type.next_element_type(), @@ -136,7 +125,7 @@ impl FinalizeTypes { array_type.next_element_type() ) } - // Ensure the type of the register matches the member type. + // Ensure the type of the register matches the element type. Operand::Register(register) => { // Retrieve the type. let plaintext_type = match self.get_type(stack, register)? { @@ -145,18 +134,25 @@ impl FinalizeTypes { // If the register is a future, throw an error. FinalizeType::Future(..) => bail!("Array element cannot be a future"), }; - // Ensure the register type matches the member type. + // Ensure the register type matches the element type. ensure!( &plaintext_type == array_type.next_element_type(), "Array element expects {}, but found '{plaintext_type}' in the operand '{operand}'.", array_type.next_element_type() ) } - // Ensure the program ID type (address) matches the member type. - Operand::ProgramID(..) => { - // Retrieve the program ID type. - let program_ref_type = PlaintextType::Literal(LiteralType::Address); - // Ensure the program ID type matches the member type. + // Ensure the program ID, block height, network ID, checksum, edition, and program owner types matches the element type. + Operand::ProgramID(..) + | Operand::BlockHeight + | Operand::NetworkID + | Operand::Checksum(_) + | Operand::Edition(_) + | Operand::ProgramOwner(_) => { + // Retrieve the operand type. + let FinalizeType::Plaintext(program_ref_type) = self.get_type_from_operand(stack, operand)? else { + bail!("Expected a plaintext type for the operand '{operand}' in array element '{array_type}'") + }; + // Ensure the operand type matches the element type. ensure!( &program_ref_type == array_type.next_element_type(), "Array element expects {}, but found '{program_ref_type}' in the operand '{operand}'.", @@ -167,28 +163,6 @@ impl FinalizeTypes { Operand::Signer => bail!("Array element cannot be cast from a signer in a finalize scope."), // If the operand is a caller, throw an error. Operand::Caller => bail!("Array element cannot be cast from a caller in a finalize scope."), - // Ensure the block height type (u32) matches the member type. - Operand::BlockHeight => { - // Retrieve the block height type. - let block_height_type = PlaintextType::Literal(LiteralType::U32); - // Ensure the block height type matches the member type. - ensure!( - &block_height_type == array_type.next_element_type(), - "Array element expects {}, but found '{block_height_type}' in the operand '{operand}'.", - 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 d79a1c0f44..e76db2ebb6 100644 --- a/synthesizer/process/src/stack/finalize_types/mod.rs +++ b/synthesizer/process/src/stack/finalize_types/mod.rs @@ -31,6 +31,7 @@ use console::{ RegisterType, StructType, }, + types::U32, }; use snarkvm_synthesizer_program::{ Await, @@ -38,6 +39,7 @@ use snarkvm_synthesizer_program::{ CallOperator, CastType, Command, + Constructor, Contains, Finalize, Get, @@ -54,7 +56,7 @@ use snarkvm_synthesizer_program::{ }; use indexmap::IndexMap; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; #[derive(Clone, Default, PartialEq, Eq)] pub struct FinalizeTypes { @@ -67,11 +69,18 @@ pub struct FinalizeTypes { } impl FinalizeTypes { + /// Initializes a new instance of `FinalizeTypes` for the given constructor. + /// Checks that the given constructor is well-formed for the given stack. + #[inline] + pub fn from_constructor(stack: &Stack, constructor: &Constructor) -> Result { + Self::initialize_finalize_types_from_constructor(stack, constructor) + } + /// Initializes a new instance of `FinalizeTypes` for the given finalize. /// Checks that the given finalize is well-formed for the given stack. #[inline] pub fn from_finalize(stack: &Stack, finalize: &Finalize) -> Result { - Self::initialize_finalize_types(stack, finalize) + Self::initialize_finalize_types_from_finalize(stack, finalize) } /// Returns `true` if the given register exists. @@ -98,6 +107,12 @@ impl FinalizeTypes { 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)), + Operand::Checksum(_) => FinalizeType::Plaintext(PlaintextType::Array(ArrayType::new( + PlaintextType::Literal(LiteralType::U8), + vec![U32::new(32)], + )?)), + Operand::Edition(_) => FinalizeType::Plaintext(PlaintextType::Literal(LiteralType::U16)), + Operand::ProgramOwner(_) => FinalizeType::Plaintext(PlaintextType::Literal(LiteralType::Address)), }) } diff --git a/synthesizer/process/src/stack/helpers/check_upgrade.rs b/synthesizer/process/src/stack/helpers/check_upgrade.rs new file mode 100644 index 0000000000..c036b7d70f --- /dev/null +++ b/synthesizer/process/src/stack/helpers/check_upgrade.rs @@ -0,0 +1,129 @@ +// 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::*; + +impl Stack { + /// Checks that the new program is a valid upgrade. + /// At a high-level, an upgrade must preserve the existing interfaces of the original program. + /// An upgrade may add new components, except for constructors, and modify logic **only** in functions and finalize scopes. + /// An upgrade may also be exactly the same as the original program. + /// + /// The order of the components in the new program may be modified, as long as the interfaces remain the same. + /// + /// An detailed overview of what an upgrade can and cannot do is given below: + /// | Program Component | Delete | Modify | Add | + /// |-------------------|--------|--------------|-------| + /// | import | ❌ | ❌ | ✅ | + /// | constructor | ❌ | ❌ | ❌ | + /// | mapping | ❌ | ❌ | ✅ | + /// | struct | ❌ | ❌ | ✅ | + /// | record | ❌ | ❌ | ✅ | + /// | closure | ❌ | ❌ | ✅ | + /// | function | ❌ | ✅ (logic) | ✅ | + /// | finalize | ❌ | ✅ (logic) | ✅ | + /// |-------------------|--------|--------------|-------| + /// + #[inline] + pub fn check_upgrade_is_valid(old_program: &Program, new_program: &Program) -> Result<()> { + // Get the new program ID. + let program_id = new_program.id(); + // Ensure the program is not `credits.aleo`. + ensure!(program_id != &ProgramID::from_str("credits.aleo")?, "Cannot upgrade 'credits.aleo'"); + // Ensure the program ID matches. + ensure!(old_program.id() == new_program.id(), "Cannot upgrade '{program_id}' with different program ID"); + // Ensure that all of the imports in the old program exist in the new program. + for old_import in old_program.imports().keys() { + if !new_program.contains_import(old_import) { + bail!("Cannot upgrade '{program_id}' because it is missing the original import '{old_import}'"); + } + } + // Ensure that the constructors in both programs are exactly the same. + // Note: Programs without constructors are not allowed to be upgraded. + match (old_program.constructor(), new_program.constructor()) { + (_, None) => bail!("A program cannot be upgraded to a program without a constructor"), + (None, _) => bail!("A program without a constructor cannot be upgraded"), + (Some(old_constructor), Some(new_constructor)) => { + ensure!( + old_constructor == new_constructor, + "Cannot upgrade '{program_id}' because the constructor does not match" + ); + } + } + // Ensure that all of the mappings in the old program exist in the new program. + for (old_mapping_id, old_mapping_type) in old_program.mappings() { + let new_mapping_type = new_program.get_mapping(old_mapping_id)?; + ensure!( + *old_mapping_type == new_mapping_type, + "Cannot upgrade '{program_id}' because the mapping '{old_mapping_id}' does not match" + ); + } + // Ensure that all of the structs in the old program exist in the new program. + for (old_struct_id, old_struct_type) in old_program.structs() { + let new_struct_type = new_program.get_struct(old_struct_id)?; + ensure!( + old_struct_type == new_struct_type, + "Cannot upgrade '{program_id}' because the struct '{old_struct_id}' does not match" + ); + } + // Ensure that all of the records in the old program exist in the new program. + for (old_record_id, old_record_type) in old_program.records() { + let new_record_type = new_program.get_record(old_record_id)?; + ensure!( + old_record_type == new_record_type, + "Cannot upgrade '{program_id}' because the record '{old_record_id}' does not match" + ); + } + // Ensure that the old program closures exist in the new program, with the exact same definition. + for old_closure in old_program.closures().values() { + let old_closure_name = old_closure.name(); + let new_closure = new_program.get_closure(old_closure_name)?; + ensure!( + old_closure == &new_closure, + "Cannot upgrade '{program_id}' because the closure '{old_closure_name}' does not match" + ); + } + // Ensure that the old program functions exist in the new program, with the same input and output types. + // If the function has an associated `finalize` block, then ensure that the finalize block exists in the new program. + for old_function in old_program.functions().values() { + let old_function_name = old_function.name(); + let new_function = new_program.get_function_ref(old_function_name)?; + ensure!( + old_function.input_types() == new_function.input_types(), + "Cannot upgrade '{program_id}' because the inputs to the function '{old_function_name}' do not match" + ); + ensure!( + old_function.output_types() == new_function.output_types(), + "Cannot upgrade '{program_id}' because the outputs of the function '{old_function_name}' do not match" + ); + match (old_function.finalize_logic(), new_function.finalize_logic()) { + (None, None) => {} // Do nothing + (None, Some(_)) => bail!( + "Cannot upgrade '{program_id}' because the function '{old_function_name}' should not have a finalize block" + ), + (Some(_), None) => bail!( + "Cannot upgrade '{program_id}' because the function '{old_function_name}' should have a finalize block" + ), + (Some(old_finalize), Some(new_finalize)) => { + ensure!( + old_finalize.input_types() == new_finalize.input_types(), + "Cannot upgrade '{program_id}' because the finalize inputs to the function '{old_function_name}' do not match" + ); + } + } + } + Ok(()) + } +} diff --git a/synthesizer/process/src/stack/helpers/initialize.rs b/synthesizer/process/src/stack/helpers/initialize.rs index 40cd33d510..e69ab14dd6 100644 --- a/synthesizer/process/src/stack/helpers/initialize.rs +++ b/synthesizer/process/src/stack/helpers/initialize.rs @@ -34,96 +34,33 @@ impl Stack { edition.checked_add(1).ok_or_else(|| anyhow!("Overflow while incrementing the program edition"))? } }; + // Construct a new stack. + let stack = Self::create_raw(process, program, edition)?; + // Initialize and check the stack's validity. + stack.initialize_and_check(process)?; + // Return the stack. + Ok(stack) + } + + /// Create a new stack, given the process and program, without completely initializing or checking for validity. + #[inline] + pub(crate) fn create_raw(process: &Process, program: &Program, edition: u16) -> Result { // Construct the stack for the program. - let mut stack = Self { + let stack = Self { program: program.clone(), stacks: Arc::downgrade(&process.stacks), + constructor_types: Default::default(), register_types: Default::default(), finalize_types: Default::default(), universal_srs: process.universal_srs().clone(), proving_keys: Default::default(), verifying_keys: Default::default(), program_address: program.id().to_address()?, + program_checksum: program.to_checksum(), program_edition: U16::new(edition), + program_owner: None, }; - - // Add all the imports into the stack. - for import in program.imports().keys() { - // Ensure that the program does not import itself. - ensure!(import != program.id(), "Program cannot import itself"); - // Ensure the program imports all exist in the process already. - if !process.contains_program(import) { - bail!("Cannot add program '{}' because its import '{import}' must be added first", program.id()) - } - } - // Add the program closures to the stack. - for closure in program.closures().values() { - // Add the closure to the stack. - stack.insert_closure(closure)?; - } - - // Add the program functions to the stack. - for function in program.functions().values() { - // Add the function to the stack. - stack.insert_function(function)?; - // Determine the number of calls for the function. - // This includes a safety check for the maximum number of calls. - stack.get_number_of_calls(function.name())?; - - // Get the finalize cost. - let finalize_cost = cost_in_microcredits_v2(&stack, function.name())?; - // Check that the finalize cost does not exceed the maximum. - ensure!( - finalize_cost <= N::TRANSACTION_SPEND_LIMIT, - "Finalize block '{}' has a cost '{finalize_cost}' which exceeds the transaction spend limit '{}'", - function.name(), - N::TRANSACTION_SPEND_LIMIT - ); - } - // Return the stack. Ok(stack) } } - -impl Stack { - /// Inserts the given closure to the stack. - #[inline] - fn insert_closure(&mut self, closure: &Closure) -> Result<()> { - // Retrieve the closure name. - let name = closure.name(); - // Ensure the closure name is not already added. - ensure!(!self.register_types.contains_key(name), "Closure '{name}' already exists"); - - // Compute the register types. - let register_types = RegisterTypes::from_closure(self, closure)?; - // Add the closure name and register types to the stack. - self.register_types.insert(*name, register_types); - // Return success. - Ok(()) - } - - /// Adds the given function name and register types to the stack. - #[inline] - fn insert_function(&mut self, function: &Function) -> Result<()> { - // Retrieve the function name. - let name = function.name(); - // Ensure the function name is not already added. - ensure!(!self.register_types.contains_key(name), "Function '{name}' already exists"); - - // Compute the register types. - let register_types = RegisterTypes::from_function(self, function)?; - // Add the function name and register types to the stack. - self.register_types.insert(*name, register_types); - - // If the function contains a finalize, insert it. - if let Some(finalize) = function.finalize_logic() { - // Compute the finalize types. - let finalize_types = FinalizeTypes::from_finalize(self, finalize)?; - // Add the finalize name and finalize types to the stack. - self.finalize_types.insert(*name, finalize_types); - } - // Return success. - Ok(()) - } -} diff --git a/synthesizer/process/src/stack/helpers/mod.rs b/synthesizer/process/src/stack/helpers/mod.rs index 5b306f41ce..1c37dd13ae 100644 --- a/synthesizer/process/src/stack/helpers/mod.rs +++ b/synthesizer/process/src/stack/helpers/mod.rs @@ -15,6 +15,7 @@ use super::*; +mod check_upgrade; mod initialize; mod sample; mod stack_trait; diff --git a/synthesizer/process/src/stack/helpers/stack_trait.rs b/synthesizer/process/src/stack/helpers/stack_trait.rs index 99a1bc3743..9cd7f1b378 100644 --- a/synthesizer/process/src/stack/helpers/stack_trait.rs +++ b/synthesizer/process/src/stack/helpers/stack_trait.rs @@ -181,11 +181,43 @@ impl StackTrait for Stack { &self.program_address } + /// Returns the program checksum. + fn program_checksum(&self) -> &[U8; 32] { + &self.program_checksum + } + + /// Returns the program checksum as a field element. + #[inline] + fn program_checksum_as_field(&self) -> Result> { + // Get the bits of the program checksum, truncated to the field size. + let bits = self + .program_checksum + .iter() + .flat_map(|byte| byte.to_bits_le()) + .take(Field::::SIZE_IN_DATA_BITS) + .collect::>(); + // Return the field element from the bits. + Field::from_bits_le(&bits) + } + /// Returns the program edition. + #[inline] fn program_edition(&self) -> U16 { self.program_edition } + /// Returns the program owner. + #[inline] + fn program_owner(&self) -> &Option> { + &self.program_owner + } + + /// Sets the program owner. + /// The program owner should only be set for programs that are deployed after `ConsensusVersion::V9` is active. + fn set_program_owner(&mut self, program_owner: Option>) { + self.program_owner = program_owner; + } + /// Returns the external stack for the given program ID. /// /// Attention - this function is used to check the existence of the external program. @@ -243,10 +275,14 @@ impl StackTrait for Stack { // Add the function to the queue. match call.operator() { CallOperator::Locator(locator) => { - queue.push(( - StackRef::External(stack_ref.get_external_stack(locator.program_id())?), - *locator.resource(), - )); + // If the locator matches the program ID of the provided stack, use it directly. + // Otherwise, retrieve the external stack. + let stack = if locator.program_id() == self.program().id() { + StackRef::Internal(self) + } else { + StackRef::External(stack_ref.get_external_stack(locator.program_id())?) + }; + queue.push((stack, *locator.resource())); } CallOperator::Resource(resource) => { queue.push((stack_ref.clone(), *resource)); diff --git a/synthesizer/process/src/stack/helpers/synthesize.rs b/synthesizer/process/src/stack/helpers/synthesize.rs index 6e1260b073..61ebc4b787 100644 --- a/synthesizer/process/src/stack/helpers/synthesize.rs +++ b/synthesizer/process/src/stack/helpers/synthesize.rs @@ -32,6 +32,11 @@ impl Stack { let program_id = self.program_id(); // Retrieve the function input types. let input_types = self.get_function(function_name)?.input_types(); + // Retrieve the program checksum, if the program has a constructor. + let program_checksum = match self.program().contains_constructor() { + true => Some(self.program_checksum_as_field()?), + false => None, + }; // Initialize a burner private key. let burner_private_key = PrivateKey::new(rng)?; @@ -50,13 +55,11 @@ impl Stack { _ => self.sample_value(&burner_address, input_type, rng), }) .collect::>>()?; - // Sample 'is_root'. + // Sample a dummy 'is_root'. let is_root = true; - - // The `root_tvk` is `None` when deploying an individual circuit. + // Sample a dummy `root_tvk` for circuit synthesis. let root_tvk = None; - - // The caller is `None` when deploying an individual circuit. + // Sample a dummy `caller` for circuit synthesis. let caller = None; // Compute the request, with a burner private key. @@ -68,6 +71,7 @@ impl Stack { &input_types, root_tvk, is_root, + program_checksum, rng, )?; // Initialize the authorization. diff --git a/synthesizer/process/src/stack/mod.rs b/synthesizer/process/src/stack/mod.rs index 3df6bba4e3..64c42bf96f 100644 --- a/synthesizer/process/src/stack/mod.rs +++ b/synthesizer/process/src/stack/mod.rs @@ -37,7 +37,7 @@ mod evaluate; mod execute; mod helpers; -use crate::{CallMetrics, Process, Trace, cost_in_microcredits_v2}; +use crate::{CallMetrics, Process, Trace, constructor_cost_in_microcredits, cost_in_microcredits_v2}; use console::{ account::{Address, PrivateKey}, network::prelude::*, @@ -95,13 +95,20 @@ use rayon::prelude::*; pub type Assignments = Arc::Field>, CallMetrics)>>>; +/// The `CallStack` is used to track the current state of the program execution. #[derive(Clone)] pub enum CallStack { + /// Authorize an `Execute` transaction. Authorize(Vec>, PrivateKey, Authorization), + /// Synthesize a function circuit before a `Deploy` transaction. Synthesize(Vec>, PrivateKey, Authorization), + /// Validate a `Deploy` transaction's function circuit. CheckDeployment(Vec>, PrivateKey, Assignments, Option, Option), + /// Evaluate a function. Evaluate(Authorization), + /// Execute a function and produce a proof. Execute(Authorization, Arc>>), + /// Execute a function and create the circuit assignment. PackageRun(Vec>, PrivateKey, Assignments), } @@ -203,10 +210,12 @@ pub struct Stack { program: Program, /// A reference to the global stack map. stacks: Weak, Arc>>>>, + /// The register types for the program constructor, if it exists. + constructor_types: Arc>>>, /// The mapping of closure and function names to their register types. - register_types: IndexMap, RegisterTypes>, + register_types: Arc, RegisterTypes>>>, /// The mapping of finalize names to their register types. - finalize_types: IndexMap, FinalizeTypes>, + finalize_types: Arc, FinalizeTypes>>>, /// The universal SRS. universal_srs: UniversalSRS, /// The mapping of function name to proving key. @@ -215,54 +224,175 @@ pub struct Stack { verifying_keys: Arc, VerifyingKey>>>, /// The program address. program_address: Address, + /// The program checksum. + program_checksum: [U8; 32], /// The program edition. program_edition: U16, + /// The program owner. + program_owner: Option>, } impl Stack { - /// Initializes a new stack, if it does not already exist, given the process and the program. + /// Initializes a new stack given the process and the program. pub fn new(process: &Process, program: &Program) -> Result { // Retrieve the program ID. let program_id = program.id(); - // Ensure the program contains functions. - ensure!(!program.functions().is_empty(), "No functions present in the deployment for program '{program_id}'"); - // If the program exists in the process, check that the new program exactly matches the existing program. + // Check that the program is well-formed. + check_program_is_well_formed(program)?; + + // If the program exists in the process, check that the new program is valid. if let Ok(existing_stack) = process.get_stack(program_id) { // Ensure the program is not `credits.aleo`. ensure!(program_id != &ProgramID::from_str("credits.aleo")?, "Cannot re-initialize the 'credits.aleo'."); - // Ensure that the new program matches the existing program. - ensure!( - existing_stack.program() == program, - "Program '{program_id}' already exists with different contents." - ); + // Get the existing program. + let existing_program = existing_stack.program(); + // If the existing program does not have a constructor, check that the new program matches the existing program. + // Otherwise, ensure that the upgrade is valid. + match existing_program.contains_constructor() { + false => ensure!( + existing_stack.program() == program, + "Program '{program_id}' already exists with different contents." + ), + true => Self::check_upgrade_is_valid(existing_program, program)?, + } } - // Serialize the program into bytes. - let program_bytes = program.to_bytes_le()?; - // Ensure the program deserializes from bytes correctly. - ensure!(program == &Program::from_bytes_le(&program_bytes)?, "Program byte serialization failed"); - - // Serialize the program into string. - let program_string = program.to_string(); - // Ensure the program deserializes from a string correctly. - ensure!(program == &Program::from_str(&program_string)?, "Program string serialization failed"); - // Return the stack. Stack::initialize(process, program) } + /// Partially initializes a new stack, given the process and the program, without checking for validity. + /// Note. This method should **NOT** be used by the on-chain VM to add new program, use `Stack::new` instead. + pub fn new_raw(process: &Process, program: &Program, edition: u16) -> Result { + // Check that the program is well-formed. + check_program_is_well_formed(program)?; + // Return the stack. + Stack::create_raw(process, program, edition) + } + + /// Initializes and checks the register state and well-formedness of the stack, even if it has already been initialized. + pub fn initialize_and_check(&self, process: &Process) -> Result<()> { + // Acquire the locks for the constructor, register, and finalize types. + let mut constructor_types = self.constructor_types.write(); + let mut register_types = self.register_types.write(); + let mut finalize_types = self.finalize_types.write(); + + // Clear the existing constructor, closure, and function types. + constructor_types.take(); + register_types.clear(); + finalize_types.clear(); + + // Add all the imports into the stack. + for import in self.program.imports().keys() { + // Ensure that the program does not import itself. + ensure!(import != self.program.id(), "Program cannot import itself"); + // Ensure the program imports all exist in the process already. + if !process.contains_program(import) { + bail!("Cannot add program '{}' because its import '{import}' must be added first", self.program.id()) + } + } + + // Add the constructor to the stack if it exists. + if let Some(constructor) = self.program.constructor() { + // Compute the constructor types. + let types = FinalizeTypes::from_constructor(self, constructor)?; + // Add the constructor types to the stack. + constructor_types.replace(types); + } + + // Add the program closures to the stack. + for closure in self.program.closures().values() { + // Retrieve the closure name. + let name = closure.name(); + // Ensure the closure name is not already added. + ensure!(!register_types.contains_key(name), "Closure '{name}' already exists"); + // Compute the register types. + let types = RegisterTypes::from_closure(self, closure)?; + // Add the closure name and register types to the stack. + register_types.insert(*name, types); + } + + // Add the program functions to the stack. + for function in self.program.functions().values() { + // Retrieve the function name. + let name = function.name(); + // Ensure the function name is not already added. + ensure!(!register_types.contains_key(name), "Function '{name}' already exists"); + // Compute the register types. + let types = RegisterTypes::from_function(self, function)?; + // Add the function name and register types to the stack. + register_types.insert(*name, types); + + // If the function contains a finalize, insert it. + if let Some(finalize) = function.finalize_logic() { + // Compute the finalize types. + let types = FinalizeTypes::from_finalize(self, finalize)?; + // Add the finalize name and finalize types to the stack. + finalize_types.insert(*name, types); + } + } + + // Drop the locks since the types have been initialized. + drop(constructor_types); + drop(register_types); + drop(finalize_types); + + // Get the constructor cost. + let constructor_cost = constructor_cost_in_microcredits(self)?; + // Check that the constructor cost does not exceed the maximum. + ensure!( + constructor_cost <= N::TRANSACTION_SPEND_LIMIT, + "Constructor has a cost '{constructor_cost}' which exceeds the transaction spend limit '{}'", + N::TRANSACTION_SPEND_LIMIT + ); + + // Check that the functions are valid. + for function in self.program.functions().values() { + // Determine the number of calls for the function. + // This includes a safety check for the maximum number of calls. + self.get_number_of_calls(function.name())?; + + // Get the finalize cost. + let finalize_cost = cost_in_microcredits_v2(self, function.name())?; + // Check that the finalize cost does not exceed the maximum. + ensure!( + finalize_cost <= N::TRANSACTION_SPEND_LIMIT, + "Finalize block '{}' has a cost '{finalize_cost}' which exceeds the transaction spend limit '{}'", + function.name(), + N::TRANSACTION_SPEND_LIMIT + ); + } + Ok(()) + } + + /// Returns the constructor types for the program. + #[inline] + pub fn get_constructor_types(&self) -> Result> { + // Retrieve the constructor types. + match self.constructor_types.read().as_ref() { + Some(constructor_types) => Ok(constructor_types.clone()), + None => bail!("Constructor types do not exist"), + } + } + /// Returns the register types for the given closure or function name. #[inline] - pub fn get_register_types(&self, name: &Identifier) -> Result<&RegisterTypes> { + pub fn get_register_types(&self, name: &Identifier) -> Result> { // Retrieve the register types. - self.register_types.get(name).ok_or_else(|| anyhow!("Register types for '{name}' do not exist")) + match self.register_types.read().get(name) { + Some(register_types) => Ok(register_types.clone()), + None => bail!("Register types for '{name}' do not exist"), + } } /// Returns the register types for the given finalize name. #[inline] - pub fn get_finalize_types(&self, name: &Identifier) -> Result<&FinalizeTypes> { + pub fn get_finalize_types(&self, name: &Identifier) -> Result> { // Retrieve the finalize types. - self.finalize_types.get(name).ok_or_else(|| anyhow!("Finalize types for '{name}' do not exist")) + match self.finalize_types.read().get(name) { + Some(finalize_types) => Ok(finalize_types.clone()), + None => bail!("Finalize types for '{name}' do not exist"), + } } /// Inserts the proving key if the program ID is 'credits.aleo'. @@ -283,8 +413,8 @@ impl Stack { impl PartialEq for Stack { fn eq(&self, other: &Self) -> bool { self.program == other.program - && self.register_types == other.register_types - && self.finalize_types == other.finalize_types + && *self.register_types.read() == *other.register_types.read() + && *self.finalize_types.read() == *other.finalize_types.read() } } @@ -309,3 +439,21 @@ impl Deref for StackRef<'_, N> { } } } + +// A helper function to check that a program is well-formed. +fn check_program_is_well_formed(program: &Program) -> Result<()> { + // Ensure the program contains functions. + ensure!(!program.functions().is_empty(), "No functions present in the deployment for program '{}'", program.id()); + + // Serialize the program into bytes. + let program_bytes = program.to_bytes_le()?; + // Ensure the program deserializes from bytes correctly. + ensure!(program == &Program::from_bytes_le(&program_bytes)?, "Program byte serialization failed"); + + // Serialize the program into string. + let program_string = program.to_string(); + // Ensure the program deserializes from a string correctly. + ensure!(program == &Program::from_str(&program_string)?, "Program string serialization failed"); + + Ok(()) +} diff --git a/synthesizer/process/src/stack/register_types/initialize.rs b/synthesizer/process/src/stack/register_types/initialize.rs index 7494855cd4..0d4759bedd 100644 --- a/synthesizer/process/src/stack/register_types/initialize.rs +++ b/synthesizer/process/src/stack/register_types/initialize.rs @@ -268,7 +268,7 @@ impl RegisterTypes { let external_stack = stack.get_external_stack(locator.program_id())?; // Ensure the external record type is defined in the program. if !external_stack.program().contains_record(locator.resource()) { - bail!("External record '{locator}' in '{}' is not defined.", stack.program_id()) + bail!("External record '{locator}' in '{}' is not defined.", external_stack.program_id()) } } RegisterType::Future(..) => bail!("Input '{register}' cannot be a future."), @@ -317,7 +317,7 @@ impl RegisterTypes { let external_stack = stack.get_external_stack(locator.program_id())?; // Ensure the external record type is defined in the program. if !external_stack.program().contains_record(locator.resource()) { - bail!("External record '{locator}' in '{}' is not defined.", stack.program_id()) + bail!("External record '{locator}' in '{}' is not defined.", external_stack.program_id()) } } RegisterType::Future(locator) => { diff --git a/synthesizer/process/src/stack/register_types/matches.rs b/synthesizer/process/src/stack/register_types/matches.rs index ef05ab0faa..7dace1e094 100644 --- a/synthesizer/process/src/stack/register_types/matches.rs +++ b/synthesizer/process/src/stack/register_types/matches.rs @@ -70,10 +70,14 @@ impl RegisterTypes { } } } - // Ensure the program ID, signer, and caller types (address) match the member type. + // Ensure the program ID, signer, and caller types match the member type. Operand::ProgramID(..) | Operand::Signer | Operand::Caller => { // Retrieve the operand type. - let operand_type = PlaintextType::Literal(LiteralType::Address); + let RegisterType::Plaintext(operand_type) = self.get_type_from_operand(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!( &operand_type == member_type, @@ -88,6 +92,24 @@ impl RegisterTypes { Operand::NetworkID => bail!( "Struct member '{struct_name}.{member_name}' cannot be from a network ID in a non-finalize scope" ), + // If the operand is a checksum type, throw an error. + Operand::Checksum(_) => { + bail!( + "Struct member '{struct_name}.{member_name}' cannot be from a checksum in a non-finalize scope" + ) + } + // If the operand is an edition type, throw an error. + Operand::Edition(_) => { + bail!( + "Struct member '{struct_name}.{member_name}' cannot be from an edition in a non-finalize scope" + ) + } + // If the operand is a program owner type, throw an error. + Operand::ProgramOwner(_) => { + bail!( + "Struct member '{struct_name}.{member_name}' cannot be from a program owner in a non-finalize scope" + ) + } } } Ok(()) @@ -144,10 +166,12 @@ impl RegisterTypes { } } } - // Ensure the program ID type, signer type, and caller types (address) match the element type. + // Ensure the program ID, signer, and caller types match the element type. Operand::ProgramID(..) | Operand::Signer | Operand::Caller => { // Retrieve the operand type. - let operand_type = PlaintextType::Literal(LiteralType::Address); + let RegisterType::Plaintext(operand_type) = self.get_type_from_operand(stack, operand)? else { + bail!("Expected a plaintext type for the operand '{operand}' in array element '{array_type}'") + }; // Ensure the operand type matches the element type. ensure!( &operand_type == array_type.next_element_type(), @@ -159,6 +183,18 @@ impl RegisterTypes { 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"), + // If the operand is a checksum type, throw an error. + Operand::Checksum(_) => { + bail!("Array element cannot be from a checksum in a non-finalize scope") + } + // If the operand is an edition type, throw an error. + Operand::Edition(_) => { + bail!("Array element cannot be from an edition in a non-finalize scope") + } + // If the operand is a program owner type, throw an error. + Operand::ProgramOwner(_) => { + bail!("Array element cannot be from a program owner in a non-finalize scope") + } } } Ok(()) @@ -219,6 +255,15 @@ impl RegisterTypes { Operand::NetworkID => { bail!("Forbidden operation: Cannot cast a network ID as a record owner") } + Operand::Checksum(_) => { + bail!("Forbidden operation: Cannot cast a checksum as a record owner") + } + Operand::Edition(_) => { + bail!("Forbidden operation: Cannot cast an edition as a record owner") + } + Operand::ProgramOwner(_) => { + bail!("Forbidden operation: Cannot cast a program owner as a record owner") + } } // Ensure the operand types match the record entry types. @@ -258,13 +303,18 @@ impl RegisterTypes { } } } - // Ensure the program ID, signer, and caller types (address) match the entry type. + // Ensure the program ID, signer, and caller types match the entry type. Operand::ProgramID(..) | Operand::Signer | Operand::Caller => { // Retrieve the operand type. - let operand_type = &PlaintextType::Literal(LiteralType::Address); + let RegisterType::Plaintext(operand_type) = self.get_type_from_operand(stack, operand)? + else { + bail!( + "Expected a plaintext type for the operand '{operand}' in record entry '{record_name}.{entry_name}'" + ) + }; // Ensure the operand type matches the entry type. ensure!( - operand_type == plaintext_type, + &operand_type == plaintext_type, "Record entry '{record_name}.{entry_name}' expects a '{plaintext_type}', but found '{operand_type}' in the operand '{operand}'.", ) } @@ -280,6 +330,24 @@ impl RegisterTypes { "Record entry '{record_name}.{entry_name}' expects a '{plaintext_type}', but found a network ID in the operand '{operand}'." ) } + // Fail if the operand is a checksum. + Operand::Checksum(_) => { + bail!( + "Record entry '{record_name}.{entry_name}' expects a '{plaintext_type}', but found a checksum in the operand '{operand}'." + ) + } + // Fail if the operand is an edition. + Operand::Edition(_) => { + bail!( + "Record entry '{record_name}.{entry_name}' expects a '{plaintext_type}', but found an edition in the operand '{operand}'." + ) + } + // Fail if the operand is a program owner. + Operand::ProgramOwner(_) => { + bail!( + "Record entry '{record_name}.{entry_name}' expects a '{plaintext_type}', but found a program owner 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 617559607c..2761056c9e 100644 --- a/synthesizer/process/src/stack/register_types/mod.rs +++ b/synthesizer/process/src/stack/register_types/mod.rs @@ -24,8 +24,10 @@ use console::{ Access, ArrayType, EntryType, + FinalizeType, Identifier, LiteralType, + Locator, PlaintextType, RecordType, Register, @@ -46,7 +48,6 @@ use snarkvm_synthesizer_program::{ StackTrait, }; -use console::program::{FinalizeType, Locator}; use indexmap::{IndexMap, IndexSet}; #[derive(Clone, Default, PartialEq, Eq)] @@ -96,6 +97,9 @@ impl RegisterTypes { } 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."), + Operand::Checksum(_) => bail!("'checksum' is not a valid operand in a non-finalize context."), + Operand::Edition(_) => bail!("'edition' is not a valid operand in a non-finalize context."), + Operand::ProgramOwner(_) => bail!("'program_owner' is not a valid operand in a non-finalize context."), }) } diff --git a/synthesizer/process/src/stack/registers/caller.rs b/synthesizer/process/src/stack/registers/caller.rs deleted file mode 100644 index 278d3cc2b1..0000000000 --- a/synthesizer/process/src/stack/registers/caller.rs +++ /dev/null @@ -1,66 +0,0 @@ -// 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::*; - -impl> RegistersSigner for Registers { - /// Returns the transition signer. - #[inline] - fn signer(&self) -> Result> { - self.signer.ok_or_else(|| anyhow!("Signer address (console) is not set in the registers.")) - } - - /// Sets the transition signer. - #[inline] - fn set_signer(&mut self, signer: Address) { - self.signer = Some(signer); - } - - /// Returns the root transition view key. - #[inline] - fn root_tvk(&self) -> Result> { - self.root_tvk.ok_or_else(|| anyhow!("Root tvk (console) is not set in the registers.")) - } - - /// Sets the root transition view key. - #[inline] - fn set_root_tvk(&mut self, root_tvk: Field) { - self.root_tvk = Some(root_tvk); - } - - /// Returns the transition caller. - #[inline] - fn caller(&self) -> Result> { - self.caller.ok_or_else(|| anyhow!("Caller address (console) is not set in the registers.")) - } - - /// Sets the transition caller. - #[inline] - fn set_caller(&mut self, caller: Address) { - self.caller = Some(caller); - } - - /// Returns the transition view key. - #[inline] - fn tvk(&self) -> Result> { - self.tvk.ok_or_else(|| anyhow!("Transition view key (console) is not set in the registers.")) - } - - /// Sets the transition view key. - #[inline] - fn set_tvk(&mut self, tvk: Field) { - self.tvk = Some(tvk); - } -} diff --git a/synthesizer/process/src/stack/registers/registers_circuit.rs b/synthesizer/process/src/stack/registers/registers_circuit.rs index d31b98d25b..cbca3a7ad5 100644 --- a/synthesizer/process/src/stack/registers/registers_circuit.rs +++ b/synthesizer/process/src/stack/registers/registers_circuit.rs @@ -104,6 +104,12 @@ impl> RegistersCircuit for Regis 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"), + // If the operand is the checksum, throw an error. + Operand::Checksum(_) => bail!("Cannot load the checksum in a non-finalize context."), + // If the operand is the edition, throw an error. + Operand::Edition(_) => bail!("Cannot load the edition in a non-finalize context"), + // If the operand is the program owner, throw an error. + Operand::ProgramOwner(_) => bail!("Cannot load the program owner in a non-finalize context"), }; // Retrieve the circuit value. diff --git a/synthesizer/process/src/stack/registers/registers_trait.rs b/synthesizer/process/src/stack/registers/registers_trait.rs index 68cb7d01f5..626866da88 100644 --- a/synthesizer/process/src/stack/registers/registers_trait.rs +++ b/synthesizer/process/src/stack/registers/registers_trait.rs @@ -90,6 +90,12 @@ impl> RegistersTrait for Registers< 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"), + // If the operand is the checksum, throw an error. + Operand::Checksum(_) => bail!("Cannot load the checksum in a non-finalize context."), + // If the operand is the edition, throw an error. + Operand::Edition(_) => bail!("Cannot load the edition in a non-finalize context"), + // If the operand is the program owner, throw an error. + Operand::ProgramOwner(_) => bail!("Cannot load the program owner in a non-finalize context"), }; // Retrieve the stack value. diff --git a/synthesizer/process/src/tests/mod.rs b/synthesizer/process/src/tests/mod.rs index fe7a0d593f..a93bb4cc69 100644 --- a/synthesizer/process/src/tests/mod.rs +++ b/synthesizer/process/src/tests/mod.rs @@ -16,4 +16,5 @@ pub mod test_credits; pub mod test_execute; pub mod test_random; +pub mod test_upgrade; pub mod test_utils; diff --git a/synthesizer/process/src/tests/test_credits.rs b/synthesizer/process/src/tests/test_credits.rs index 3218fc521d..addffa4829 100644 --- a/synthesizer/process/src/tests/test_credits.rs +++ b/synthesizer/process/src/tests/test_credits.rs @@ -2842,14 +2842,28 @@ mod sanity_checks { let program_id = *program.id(); // Retrieve the input types. let input_types = program.get_function(&function_name).unwrap().input_types(); + // Retrieve the program checksum, if the program has a constructor. + let program_checksum = match stack.program().contains_constructor() { + true => Some(stack.program_checksum_as_field().unwrap()), + false => None, + }; // Sample 'root_tvk'. let root_tvk = None; // Sample 'is_root'. let is_root = true; // Compute the request. - let request = - Request::sign(private_key, program_id, function_name, inputs.iter(), &input_types, root_tvk, is_root, rng) - .unwrap(); + let request = Request::sign( + private_key, + program_id, + function_name, + inputs.iter(), + &input_types, + root_tvk, + is_root, + program_checksum, + rng, + ) + .unwrap(); // Initialize the assignments. let assignments = Assignments::::default(); // Initialize the call stack. diff --git a/synthesizer/process/src/tests/test_upgrade.rs b/synthesizer/process/src/tests/test_upgrade.rs new file mode 100644 index 0000000000..6bc6cf2bc9 --- /dev/null +++ b/synthesizer/process/src/tests/test_upgrade.rs @@ -0,0 +1,1074 @@ +// 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. + +/// The purpose of these tests are to ensure that an upgrade made to a program is syntactically correct. +/// These rules are defined in `check_upgrade_is_valid`. +/// These tests *DO NOT*: check the semantic correctness of the upgrades. +use crate::{Process, Stack}; +use console::network::{MainnetV0, prelude::*}; +use snarkvm_synthesizer_program::{Program, StackTrait}; + +type CurrentNetwork = MainnetV0; + +// A helper function to sample the default process. +fn sample_process() -> Result, Error> { + let mut process = Process::load()?; + // Add the default program to the process. + let default_program = Program::from_str( + r" +program test.aleo; +function foo: +constructor: + assert.eq true true; + ", + )?; + process.add_program(&default_program)?; + // Return the process. + Ok(process) +} + +#[test] +fn test_add_simple_program() -> Result<()> { + // Sample the default process. + let mut process = Process::::load()?; + // Add a simple program to the process. + let initial_program = Program::from_str( + r" +program test.aleo; +function foo: + ", + )?; + // Add the new program to the process. + process.add_program(&initial_program)?; + // Get the program from the process. + let stack = process.get_stack("test.aleo")?; + let program = stack.program(); + // Check that the program is the same as the initial program. + assert_eq!(program, &initial_program); + Ok(()) +} + +#[test] +fn test_upgrade_without_constructor() -> Result<()> { + // Sample the default process. + let mut process = Process::::load()?; + // Add a program without a constructor to the process. + let initial_program = Program::from_str( + r" +program test.aleo; +function foo: + ", + )?; + process.add_program(&initial_program)?; + // Attempt to upgrade the program. + let new_program = Program::from_str( + r" +program test.aleo; +function foo: +function bar: + ", + )?; + // Verify that the upgrade was not successful. + assert!(process.add_program(&new_program).is_err()); + Ok(()) +} + +#[test] +fn test_upgrade_with_constructor() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Upgrade the program. + let new_program = Program::from_str( + r" +program test.aleo; +constructor: + assert.eq true true; +function foo: +function bar: + ", + )?; + // Verify that the upgrade was successful. + process.add_program(&new_program)?; + Ok(()) +} + +#[test] +fn test_add_import() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Upgrade the program. + let new_program = Program::from_str( + r" +import credits.aleo; +program test.aleo; +constructor: + assert.eq true true; +function foo: + ", + )?; + process.add_program(&new_program)?; + // Verify that the upgrade was successful. + let stack = process.get_stack("test.aleo")?; + let program = stack.program(); + assert_eq!(program, &new_program); + Ok(()) +} + +#[test] +fn test_add_struct() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Upgrade the program. + let new_program = Program::from_str( + r" +program test.aleo; +constructor: + assert.eq true true; +struct bar: + data as u8; +function foo: + ", + )?; + process.add_program(&new_program)?; + // Verify that the upgrade was successful. + let stack = process.get_stack("test.aleo")?; + let program = stack.program(); + assert_eq!(program, &new_program); + Ok(()) +} + +#[test] +fn test_add_record() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Upgrade the program. + let new_program = Program::from_str( + r" +program test.aleo; +constructor: + assert.eq true true; +record bar: + owner as address.private; + data as u8.private; +function foo: + ", + )?; + process.add_program(&new_program)?; + // Verify that the upgrade was successful. + let stack = process.get_stack("test.aleo")?; + let program = stack.program(); + assert_eq!(program, &new_program); + Ok(()) +} + +#[test] +fn test_add_mapping() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Upgrade the program. + let new_program = Program::from_str( + r" +program test.aleo; +constructor: + assert.eq true true; +mapping onchain: + key as u8.public; + value as u16.public; +function foo: + ", + )?; + process.add_program(&new_program)?; + // Verify that the upgrade was successful. + let stack = process.get_stack("test.aleo")?; + let program = stack.program(); + assert_eq!(program, &new_program); + Ok(()) +} + +#[test] +fn test_add_closure() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Upgrade the program. + let new_program = Program::from_str( + r" +program test.aleo; +constructor: + assert.eq true true; +closure sum: + input r0 as u8; + input r1 as u8; + add r0 r1 into r2; + output r2 as u8; +function foo: + ", + )?; + process.add_program(&new_program)?; + // Verify that the upgrade was successful. + let stack = process.get_stack("test.aleo")?; + let program = stack.program(); + assert_eq!(program, &new_program); + Ok(()) +} + +#[test] +fn test_add_function() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Upgrade the program. + let new_program = Program::from_str( + r" +program test.aleo; +constructor: + assert.eq true true; +function adder: + input r0 as u8.private; + input r1 as u8.private; + add r0 r1 into r2; + output r2 as u8.private; +function foo: + ", + )?; + process.add_program(&new_program)?; + // Verify that the upgrade was successful. + let stack = process.get_stack("test.aleo")?; + let program = stack.program(); + assert_eq!(program, &new_program); + Ok(()) +} + +#[test] +fn test_modify_function_logic() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Add the initial program to the process. + let initial_program = Program::from_str( + r" +program basic.aleo; +constructor: + assert.eq true true; +function adder: + input r0 as u8.private; + input r1 as u8.private; + add r0 r1 into r2; + output r2 as u8.private; + ", + )?; + process.add_program(&initial_program)?; + // Upgrade the program. + let new_program = Program::from_str( + r" +program basic.aleo; +constructor: + assert.eq true true; +function adder: + input r0 as u8.private; + input r1 as u8.private; + sub r0 r1 into r2; + output r2 as u8.private; + ", + )?; + process.add_program(&new_program)?; + // Verify that the upgrade was successful. + let stack = process.get_stack("basic.aleo")?; + let program = stack.program(); + assert_eq!(program, &new_program); + Ok(()) +} + +#[test] +fn test_modify_function_signature() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Add the initial program to the process. + let initial_program = Program::from_str( + r" +program basic.aleo; +constructor: + assert.eq true true; +function adder: + input r0 as u8.private; + input r1 as u8.private; + add r0 r1 into r2; + output r2 as u8.private; + ", + )?; + process.add_program(&initial_program)?; + // Upgrade the program. + let new_program = Program::from_str( + r" +program basic.aleo; +constructor: + assert.eq true true; +function adder: + input r0 as u16.private; + input r1 as u16.private; + add r0 r1 into r2; + output r2 as u16.private; + ", + )?; + // Verify that the upgrade was not successful. + assert!(process.add_program(&new_program).is_err()); + Ok(()) +} + +#[test] +fn test_modify_finalize_logic() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Add the initial program to the process. + let initial_program = Program::from_str( + r" +program basic.aleo; +constructor: + assert.eq true true; +function assert_on_chain: + input r0 as u8.public; + input r1 as u8.public; + async assert_on_chain r0 r1 into r2; + output r2 as basic.aleo/assert_on_chain.future; +finalize assert_on_chain: + input r0 as u8.public; + input r1 as u8.public; + assert.eq r0 r1; + ", + )?; + process.add_program(&initial_program)?; + // Upgrade the program. + let new_program = Program::from_str( + r" +program basic.aleo; +constructor: + assert.eq true true; +function assert_on_chain: + input r0 as u8.public; + input r1 as u8.public; + async assert_on_chain r0 r1 into r2; + output r2 as basic.aleo/assert_on_chain.future; +finalize assert_on_chain: + input r0 as u8.public; + input r1 as u8.public; + assert.neq r0 r1; + ", + )?; + process.add_program(&new_program)?; + // Verify that the upgrade was successful. + let stack = process.get_stack("basic.aleo")?; + let program = stack.program(); + assert_eq!(program, &new_program); + Ok(()) +} + +#[test] +fn test_modify_finalize_signature() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Add the initial program to the process. + let initial_program = Program::from_str( + r" +program basic.aleo; +constructor: + assert.eq true true; +function assert_on_chain: + input r0 as u8.public; + input r1 as u8.public; + async assert_on_chain r0 r1 into r2; + output r2 as basic.aleo/assert_on_chain.future; +finalize assert_on_chain: + input r0 as u8.public; + input r1 as u8.public; + assert.eq r0 r1; + ", + )?; + process.add_program(&initial_program)?; + // Upgrade the program. + let new_program = Program::from_str( + r" +program basic.aleo; +constructor: + assert.eq true true; +function assert_on_chain: + input r0 as u8.public; + input r1 as u8.public; + async assert_on_chain 0u16 1u16 into r2; + output r2 as basic.aleo/assert_on_chain.future; +finalize assert_on_chain: + input r0 as u16.public; + input r1 as u16.public; + assert.eq r0 r1; + ", + )?; + // Verify that the upgrade was not successful. + assert!(process.add_program(&new_program).is_err()); + Ok(()) +} + +#[test] +fn test_modify_struct() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Add the initial program to the process. + let initial_program = Program::from_str( + r" +program basic.aleo; +constructor: + assert.eq true true; +struct bar: + data as u8; +function foo: + ", + )?; + process.add_program(&initial_program)?; + // Upgrade the program. + let new_program = Program::from_str( + r" +program basic.aleo; +constructor: + assert.eq true true; +struct bar: + data as u16; +function foo: + ", + )?; + // Verify that the upgrade was not successful. + assert!(process.add_program(&new_program).is_err()); + Ok(()) +} + +#[test] +fn test_modify_record() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Add the initial program to the process. + let initial_program = Program::from_str( + r" +program basic.aleo; +constructor: + assert.eq true true; +record bar: + owner as address.private; + data as u8.private; +function foo: + ", + )?; + process.add_program(&initial_program)?; + // Upgrade the program. + let new_program = Program::from_str( + r" +program basic.aleo; +constructor: + assert.eq true true; +record bar: + owner as address.private; + data as u16.private; +function foo: + ", + )?; + // Verify that the upgrade was not successful. + assert!(process.add_program(&new_program).is_err()); + Ok(()) +} + +#[test] +fn test_modify_mapping() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Add the initial program to the process. + let initial_program = Program::from_str( + r" +program basic.aleo; +constructor: + assert.eq true true; +mapping onchain: + key as u8.public; + value as u16.public; +function foo: + ", + )?; + process.add_program(&initial_program)?; + // Upgrade the program. + let new_program = Program::from_str( + r" +program basic.aleo; +constructor: + assert.eq true true; +mapping onchain: + key as u8.public; + value as u8.public; +function foo: + ", + )?; + // Verify that the upgrade was not successful. + assert!(process.add_program(&new_program).is_err()); + Ok(()) +} + +#[test] +fn test_modify_closure_logic() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Add the initial program to the process. + let initial_program = Program::from_str( + r" +program basic.aleo; +constructor: + assert.eq true true; +closure sum: + input r0 as u8; + input r1 as u8; + add r0 r1 into r2; + output r2 as u8; +function foo: + ", + )?; + process.add_program(&initial_program)?; + // Upgrade the program. + let new_program = Program::from_str( + r" +program basic.aleo; +constructor: + assert.eq true true; +closure sum: + input r0 as u8; + input r1 as u8; + sub r0 r1 into r2; + output r2 as u8; +function foo: + ", + )?; + // Verify that the upgrade was not successful. + assert!(process.add_program(&new_program).is_err()); + Ok(()) +} + +#[test] +fn test_modify_closure_signature() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Add the initial program to the process. + let initial_program = Program::from_str( + r" +program basic.aleo; +constructor: + assert.eq true true; +closure sum: + input r0 as u8; + input r1 as u8; + add r0 r1 into r2; + output r2 as u8; +function foo: + ", + )?; + process.add_program(&initial_program)?; + // Upgrade the program. + let new_program = Program::from_str( + r" +program basic.aleo; +constructor: + assert.eq true true; +closure sum: + input r0 as u16; + input r1 as u16; + add r0 r1 into r2; + output r2 as u16; +function foo: + ", + )?; + // Verify that the upgrade was not successful. + assert!(process.add_program(&new_program).is_err()); + Ok(()) +} + +#[test] +fn test_remove_import() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Add the initial program to the process. + let initial_program = Program::from_str( + r" +import credits.aleo; +program basic.aleo; +constructor: + assert.eq true true; +function foo: + ", + )?; + process.add_program(&initial_program)?; + // Upgrade the program. + let new_program = Program::from_str( + r" +program basic.aleo; +constructor: + assert.eq true true; +function foo: + ", + )?; + // Verify that the upgrade was not successful. + assert!(process.add_program(&new_program).is_err()); + Ok(()) +} + +#[test] +fn test_remove_struct() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Add the initial program to the process. + let initial_program = Program::from_str( + r" +program basic.aleo; +constructor: + assert.eq true true; +struct bar: + data as u8; +function foo: + ", + )?; + process.add_program(&initial_program)?; + // Upgrade the program. + let new_program = Program::from_str( + r" +program basic.aleo; +constructor: + assert.eq true true; +function foo: + ", + )?; + // Verify that the upgrade was not successful. + assert!(process.add_program(&new_program).is_err()); + Ok(()) +} + +#[test] +fn test_remove_record() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Add the initial program to the process. + let initial_program = Program::from_str( + r" +program basic.aleo; +constructor: + assert.eq true true; +record bar: + owner as address.private; + data as u8.private; +function foo: + ", + )?; + process.add_program(&initial_program)?; + // Upgrade the program. + let new_program = Program::from_str( + r" +program basic.aleo; +constructor: + assert.eq true true; +function foo: + ", + )?; + // Verify that the upgrade was not successful. + assert!(process.add_program(&new_program).is_err()); + Ok(()) +} + +#[test] +fn test_remove_mapping() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Add the initial program to the process. + let initial_program = Program::from_str( + r" +program basic.aleo; +constructor: + assert.eq true true; +mapping onchain: + key as u8.public; + value as u16.public; +function foo: + ", + )?; + process.add_program(&initial_program)?; + // Upgrade the program. + let new_program = Program::from_str( + r" +program basic.aleo; +constructor: + assert.eq true true; +function foo: + ", + )?; + // Verify that the upgrade was not successful. + assert!(process.add_program(&new_program).is_err()); + Ok(()) +} + +#[test] +fn test_remove_closure() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Add the initial program to the process. + let initial_program = Program::from_str( + r" +program basic.aleo; +constructor: + assert.eq true true; +closure sum: + input r0 as u8; + input r1 as u8; + add r0 r1 into r2; + output r2 as u8; +function foo: + ", + )?; + process.add_program(&initial_program)?; + // Upgrade the program. + let new_program = Program::from_str( + r" +program basic.aleo; +constructor: + assert.eq true true; +function foo: + ", + )?; + // Verify that the upgrade was not successful. + assert!(process.add_program(&new_program).is_err()); + Ok(()) +} + +#[test] +fn test_remove_function() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Add the initial program to the process. + let initial_program = Program::from_str( + r" +program basic.aleo; +constructor: + assert.eq true true; +function adder: + input r0 as u8.private; + input r1 as u8.private; + add r0 r1 into r2; + output r2 as u8.private; +function foo: + ", + )?; + process.add_program(&initial_program)?; + // Upgrade the program. + let new_program = Program::from_str( + r" +program basic.aleo; +constructor: + assert.eq true true; +function foo: + ", + )?; + // Verify that the upgrade was not successful. + assert!(process.add_program(&new_program).is_err()); + Ok(()) +} + +#[test] +fn test_add_call_to_non_async_transition() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Add a program with a non-async transition. + let new_program = Program::from_str( + r" +program non_async.aleo; +constructor: + assert.eq true true; +function foo: + input r0 as u8.private; + input r1 as u8.private; + add r0 r1 into r2; + output r2 as u8.private;", + )?; + process.add_program(&new_program)?; + // Add the initial program to the process. + let initial_program = Program::from_str( + r" +import non_async.aleo; +program basic.aleo; +constructor: + assert.eq true true; +function adder: + input r0 as u8.private; + input r1 as u8.private; + add r0 r1 into r2; + output r2 as u8.private; + ", + )?; + process.add_program(&initial_program)?; + // Upgrade the program. + let new_program = Program::from_str( + r" +import non_async.aleo; +program basic.aleo; +constructor: + assert.eq true true; +function adder: + input r0 as u8.private; + input r1 as u8.private; + call non_async.aleo/foo r0 r1 into r2; + output r2 as u8.private; + ", + )?; + process.add_program(&new_program)?; + // Verify that the upgrade was successful. + let stack = process.get_stack("basic.aleo")?; + let program = stack.program(); + assert_eq!(program, &new_program); + Ok(()) +} + +#[test] +fn test_add_call_to_async_transition() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Add a program with an async transition. + let new_program = Program::from_str( + r" +program async_example.aleo; +constructor: + assert.eq true true; +function foo: + input r0 as u8.private; + input r1 as u8.private; + async foo r0 r1 into r2; + add r0 r1 into r3; + output r3 as u8.private; + output r2 as async_example.aleo/foo.future; +finalize foo: + input r0 as u8.public; + input r1 as u8.public; + assert.eq r0 r1;", + )?; + process.add_program(&new_program)?; + // Add the initial program to the process. + let initial_program = Program::from_str( + r" +import async_example.aleo; +program basic.aleo; +constructor: + assert.eq true true; +function adder: + input r0 as u8.private; + input r1 as u8.private; + add r0 r1 into r2; + output r2 as u8.private; + ", + )?; + process.add_program(&initial_program)?; + // Upgrade the program. + let new_program = Program::from_str( + r" +import async_example.aleo; +program basic.aleo; +constructor: + assert.eq true true; +function adder: + input r0 as u8.private; + input r1 as u8.private; + call async_example.aleo/foo r0 r1 into r2 r3; + async adder r3 into r4; + output r2 as u8.private; + output r4 as basic.aleo/adder.future; +finalize adder: + input r0 as async_example.aleo/foo.future; + await r0;", + )?; + // Verify that the upgrade was not successful. + assert!(process.add_program(&new_program).is_err()); + Ok(()) +} + +#[test] +fn test_add_import_cycle() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + + // Add the initial program to the process. + let initial_program = Program::from_str( + r" +program basic.aleo; +constructor: + assert.eq true true; +function adder: + input r0 as u8.private; + input r1 as u8.private; + add r0 r1 into r2; + output r2 as u8.private; + ", + )?; + process.add_program(&initial_program)?; + + // Verify that self-import cycles are not allowed. + let new_program = Program::from_str( + r" +import basic.aleo; +program basic.aleo; +constructor: + assert.eq true true; +function adder: + input r0 as u8.private; + input r1 as u8.private; + add r0 r1 into r2; + output r2 as u8.private; + ", + )?; + assert!(process.add_program(&new_program).is_err()); + + // Add a program dependent on `basic.aleo`. + let dependent_program = Program::from_str( + r" +import basic.aleo; +program dependent.aleo; +constructor: + assert.eq true true; +function foo: + input r0 as u8.private; + input r1 as u8.private; + call basic.aleo/adder r0 r1 into r2; + output r2 as u8.private;", + )?; + process.add_program(&dependent_program)?; + // Verify that the upgrade was successful. + let stack = process.get_stack("dependent.aleo")?; + let program = stack.program(); + assert_eq!(program, &dependent_program); + + // Upgrade basic.aleo to import dependent.aleo. + // This is allowed since we do not do cycle detection across programs. + let new_program = Program::from_str( + r" +import dependent.aleo; +program basic.aleo; +constructor: + assert.eq true true; +function adder: + input r0 as u8.private; + input r1 as u8.private; + add r0 r1 into r2; + output r2 as u8.private; + ", + )?; + // Verify that the upgrade was successful. + process.add_program(&new_program)?; + Ok(()) +} + +#[test] +fn test_constructor_upgrade() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + // Add the initial program to the process. + let initial_program = Program::from_str( + r" +program basic.aleo; +function foo: +constructor: + assert.eq 1u8 1u8; + ", + )?; + process.add_program(&initial_program)?; + // Upgrade the program. + let new_program = Program::from_str( + r" +program basic.aleo; +function foo: +constructor: + assert.eq 2u8 2u8; + ", + )?; + // Verify that the upgrade was not successful. + assert!(process.add_program(&new_program).is_err()); + Ok(()) +} + +#[test] +fn test_credits_is_not_upgradable() -> Result<()> { + // Sample the default process. + let process = sample_process()?; + // Define the new credits program. + let credits_program = Program::::credits()?; + let program = Program::from_str(&format!("{credits_program}\nfunction dummy:"))?; + // Attempt to add the new credits program. + let stack = process.get_stack("credits.aleo")?; + let old_program = stack.program(); + let result = Stack::check_upgrade_is_valid(old_program, &program); + // Verify that the upgrade was not successful. + assert!(result.is_err(), "Upgrading 'credits.aleo' should not be allowed"); + Ok(()) +} + +#[test] +fn test_edition_and_checksum_and_program_owner_operands() -> Result<()> { + // Sample the default process. + let mut process = sample_process()?; + + // A helper to sample a test program with an operand in the function. + let sample_program_with_operand_in_function = |operand: &str| -> Result> { + Program::from_str(&format!( + r" +program test.aleo; + +constructor: + assert.eq true true; + +function foo: + assert.eq {operand} {operand}; + ", + )) + }; + + // A helper to sample a test program with an operand in the closure. + let sample_program_with_operand_in_closure = |operand: &str| -> Result> { + Program::from_str(&format!( + r" +program test.aleo; + +constructor: + assert.eq true true; + +function dummy: + +closure foo: + input r0 as u8; + assert.eq {operand} {operand}; + ", + )) + }; + + // Check that the below programs are invalid. + + let program = sample_program_with_operand_in_function("edition")?; + assert!(process.add_program(&program).is_err()); + + let program = sample_program_with_operand_in_function("checksum")?; + assert!(process.add_program(&program).is_err()); + + let program = sample_program_with_operand_in_function("program_owner")?; + assert!(process.add_program(&program).is_err()); + + let program = sample_program_with_operand_in_closure("edition")?; + assert!(process.add_program(&program).is_err()); + + let program = sample_program_with_operand_in_closure("checksum")?; + assert!(process.add_program(&program).is_err()); + + let program = sample_program_with_operand_in_closure("program_owner")?; + assert!(process.add_program(&program).is_err()); + + Ok(()) +} diff --git a/synthesizer/process/src/traits/mod.rs b/synthesizer/process/src/traits/mod.rs deleted file mode 100644 index df16bc3010..0000000000 --- a/synthesizer/process/src/traits/mod.rs +++ /dev/null @@ -1,97 +0,0 @@ -// 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 crate::{CallStack, Closure, FinalizeTypes, RegisterTypes}; -use console::{ - account::Address, - network::Network, - prelude::{CryptoRng, Result, Rng}, - program::{Identifier, ProgramID, Response, Value}, - types::Field, -}; - -pub trait StackEvaluate: Clone { - /// Evaluates a program closure on the given inputs. - /// - /// # Errors - /// This method will halt if the given inputs are not the same length as the input statements. - fn evaluate_closure>( - &self, - closure: &Closure, - inputs: &[Value], - call_stack: CallStack, - signer: Address, - caller: Address, - tvk: Field, - ) -> Result>>; - - /// Evaluates a program function on the given inputs. - /// - /// # Errors - /// This method will halt if the given inputs are not the same length as the input statements. - fn evaluate_function, R: CryptoRng + Rng>( - &self, - call_stack: CallStack, - caller: Option>, - root_tvk: Option>, - rng: &mut R, - ) -> Result>; -} - -pub trait StackExecute { - /// Executes a program closure on the given inputs. - /// - /// # Errors - /// This method will halt if the given inputs are not the same length as the input statements. - fn execute_closure>( - &self, - closure: &Closure, - inputs: &[circuit::Value], - call_stack: CallStack, - signer: circuit::Address, - caller: circuit::Address, - tvk: circuit::Field, - ) -> Result>>; - - /// Executes a program function on the given inputs. - /// - /// Note: To execute a transition, do **not** call this method. Instead, call `Process::execute`. - /// - /// # Errors - /// This method will halt if the given inputs are not the same length as the input statements. - fn execute_function, R: CryptoRng + Rng>( - &self, - call_stack: CallStack, - console_caller: Option>, - root_tvk: Option>, - rng: &mut R, - ) -> Result>; -} - -pub trait StackProgramTypes { - /// Returns the register types for the given closure or function name. - fn get_register_types(&self, name: &Identifier) -> Result<&RegisterTypes>; - - /// Returns the register types for the given finalize name. - fn get_finalize_types(&self, name: &Identifier) -> Result<&FinalizeTypes>; -} - -pub trait RegistersCall { - /// Returns the current call stack. - fn call_stack(&self) -> CallStack; - - /// Returns a reference to the current call stack. - fn call_stack_ref(&self) -> &CallStack; -} diff --git a/synthesizer/process/src/verify_deployment.rs b/synthesizer/process/src/verify_deployment.rs index 84075bd58f..6f30e846b8 100644 --- a/synthesizer/process/src/verify_deployment.rs +++ b/synthesizer/process/src/verify_deployment.rs @@ -42,6 +42,8 @@ impl Process { } // Ensure the program is well-formed, by computing the stack. + // Note: The program owner is intentionally not set, since `program_owner` is an operand + // that is only available in a finalize scope. let stack = Stack::new(self, deployment.program())?; lap!(timer, "Compute the stack"); diff --git a/synthesizer/process/src/verify_execution.rs b/synthesizer/process/src/verify_execution.rs index cabf8d8b2f..44edc5a9b4 100644 --- a/synthesizer/process/src/verify_execution.rs +++ b/synthesizer/process/src/verify_execution.rs @@ -37,7 +37,7 @@ impl Process { Transaction::::MAX_TRANSITIONS ); - // Ensure the number of transitions matches the program function. + // Determine the function locator and ensure the number of transitions matches the number of calls. let locator = { // Retrieve the transition (without popping it). let transition = execution.peek()?; @@ -132,6 +132,11 @@ impl Process { let stack = self.get_stack(transition.program_id())?; // Retrieve the function from the stack. let function = stack.get_function(transition.function_name())?; + // Retrieve the program checksum, if the program has a constructor. + let program_checksum = match stack.program().contains_constructor() { + true => Some(stack.program_checksum_as_field()?), + false => None, + }; // Ensure the number of inputs and outputs match the expected number in the function. ensure!(function.inputs().len() == num_inputs, "The number of transition inputs is incorrect"); @@ -150,7 +155,13 @@ impl Process { let parent = reverse_call_graph.get(transition.id()).and_then(|tid| execution.get_program_id(tid)); // Construct the verifier inputs for the transition. - let inputs = self.to_transition_verifier_inputs(transition, parent, &call_graph, &mut transition_map)?; + let inputs = self.to_transition_verifier_inputs( + transition, + parent, + &call_graph, + program_checksum.map(|checksum| *checksum), + &mut transition_map, + )?; lap!(timer, "Constructed the verifier inputs for a transition of {}", function.name()); // Save the verifying key and its inputs. @@ -200,6 +211,7 @@ impl Process { transition: &Transition, parent: Option<&ProgramID>, call_graph: &HashMap>, + program_checksum: Option, transition_map: &mut HashMap>, ) -> Result> { // Compute the x- and y-coordinate of `tpk`. @@ -221,7 +233,13 @@ impl Process { let (parent_x, parent_y) = parent_address.to_xy_coordinates(); // [Inputs] Construct the verifier inputs to verify the proof. - let mut inputs = vec![N::Field::one(), *tpk_x, *tpk_y, **transition.tcm(), **transition.scm()]; + let mut inputs = vec![N::Field::one()]; + // [Inputs] Extend the verifier inputs with the program checksum if it was provided. + if let Some(program_checksum) = program_checksum { + inputs.push(program_checksum); + } + // [Inputs] Extend the verifier inputs with the tpk, transition and signer commitments. + inputs.extend([*tpk_x, *tpk_y, **transition.tcm(), **transition.scm()]); // [Inputs] Extend the verifier inputs with the input IDs. inputs.extend(transition.inputs().iter().flat_map(|input| input.verifier_inputs())); // [Inputs] Extend the verifier inputs with the public inputs for 'self.caller'. diff --git a/synthesizer/process/src/verify_fee.rs b/synthesizer/process/src/verify_fee.rs index 305a580e03..56256889e2 100644 --- a/synthesizer/process/src/verify_fee.rs +++ b/synthesizer/process/src/verify_fee.rs @@ -271,10 +271,12 @@ mod tests { // Fetch transactions. let transactions = [ - snarkvm_ledger_test_helpers::sample_deployment_transaction(0, true, rng), - snarkvm_ledger_test_helpers::sample_deployment_transaction(0, false, rng), - snarkvm_ledger_test_helpers::sample_execution_transaction_with_fee(true, rng), - snarkvm_ledger_test_helpers::sample_execution_transaction_with_fee(false, rng), + snarkvm_ledger_test_helpers::sample_deployment_transaction(1, Uniform::rand(rng), true, rng), + snarkvm_ledger_test_helpers::sample_deployment_transaction(1, Uniform::rand(rng), false, rng), + snarkvm_ledger_test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), true, rng), + snarkvm_ledger_test_helpers::sample_deployment_transaction(2, Uniform::rand(rng), false, rng), + snarkvm_ledger_test_helpers::sample_execution_transaction_with_fee(true, rng, 0), + snarkvm_ledger_test_helpers::sample_execution_transaction_with_fee(false, rng, 0), snarkvm_ledger_test_helpers::sample_fee_private_transaction(rng), snarkvm_ledger_test_helpers::sample_fee_public_transaction(rng), ]; diff --git a/synthesizer/program/Cargo.toml b/synthesizer/program/Cargo.toml index 4758018b6d..d4f2af1614 100644 --- a/synthesizer/program/Cargo.toml +++ b/synthesizer/program/Cargo.toml @@ -64,6 +64,10 @@ features = [ "preserve_order" ] [dependencies.snarkvm-synthesizer-snark] workspace = true +[dependencies.tiny-keccak] +version = "2" +features = [ "sha3" ] + [dev-dependencies.bincode] workspace = true diff --git a/synthesizer/program/src/bytes.rs b/synthesizer/program/src/bytes.rs index 124603492b..2dc508568b 100644 --- a/synthesizer/program/src/bytes.rs +++ b/synthesizer/program/src/bytes.rs @@ -28,13 +28,13 @@ impl FromBytes for ProgramCore { let id = ProgramID::read_le(&mut reader)?; // Initialize the program. - let mut program = ProgramCore::new(id).map_err(|e| error(e.to_string()))?; + let mut program = ProgramCore::new(id).map_err(error)?; // Read the number of program imports. let imports_len = u8::read_le(&mut reader)?; // Read the program imports. for _ in 0..imports_len { - program.add_import(Import::read_le(&mut reader)?).map_err(|e| error(e.to_string()))?; + program.add_import(Import::read_le(&mut reader)?).map_err(error)?; } // Read the number of components. @@ -45,15 +45,17 @@ impl FromBytes for ProgramCore { // Match the variant. match variant { // Read the mapping. - 0 => program.add_mapping(Mapping::read_le(&mut reader)?).map_err(|e| error(e.to_string()))?, + 0 => program.add_mapping(Mapping::read_le(&mut reader)?).map_err(error)?, // Read the struct. - 1 => program.add_struct(StructType::read_le(&mut reader)?).map_err(|e| error(e.to_string()))?, + 1 => program.add_struct(StructType::read_le(&mut reader)?).map_err(error)?, // Read the record. - 2 => program.add_record(RecordType::read_le(&mut reader)?).map_err(|e| error(e.to_string()))?, + 2 => program.add_record(RecordType::read_le(&mut reader)?).map_err(error)?, // Read the closure. - 3 => program.add_closure(ClosureCore::read_le(&mut reader)?).map_err(|e| error(e.to_string()))?, + 3 => program.add_closure(ClosureCore::read_le(&mut reader)?).map_err(error)?, // Read the function. - 4 => program.add_function(FunctionCore::read_le(&mut reader)?).map_err(|e| error(e.to_string()))?, + 4 => program.add_function(FunctionCore::read_le(&mut reader)?).map_err(error)?, + // Read the constructor. + 5 => program.add_constructor(ConstructorCore::read_le(&mut reader)?).map_err(error)?, // Invalid variant. _ => return Err(error(format!("Failed to parse program. Invalid component variant '{variant}'"))), } @@ -72,65 +74,81 @@ impl ToBytes for ProgramCore { self.id.write_le(&mut writer)?; // Write the number of program imports. - u8::try_from(self.imports.len()).map_err(|e| error(e.to_string()))?.write_le(&mut writer)?; + u8::try_from(self.imports.len()).map_err(error)?.write_le(&mut writer)?; // Write the program imports. for import in self.imports.values() { import.write_le(&mut writer)?; } // Write the number of components. - u16::try_from(self.components.len()).map_err(|e| error(e.to_string()))?.write_le(&mut writer)?; + u16::try_from(self.components.len()).map_err(error)?.write_le(&mut writer)?; + // Write the components. - for (identifier, definition) in self.components.iter() { - match definition { - ProgramDefinition::Mapping => match self.mappings.get(identifier) { - Some(mapping) => { + for (label, definition) in self.components.iter() { + match label { + ProgramLabel::Constructor => { + // Write the constructor, if it exists. + if let Some(constructor) = &self.constructor { // Write the variant. - 0u8.write_le(&mut writer)?; - // Write the mapping. - mapping.write_le(&mut writer)?; + 5u8.write_le(&mut writer)?; + // Write the constructor. + constructor.write_le(&mut writer)?; } - None => return Err(error(format!("Mapping '{identifier}' is not defined"))), - }, - ProgramDefinition::Struct => match self.structs.get(identifier) { - Some(struct_) => { - // Write the variant. - 1u8.write_le(&mut writer)?; - // Write the struct. - struct_.write_le(&mut writer)?; + } + ProgramLabel::Identifier(identifier) => { + match definition { + ProgramDefinition::Constructor => { + return Err(error("A program constructor cannot have a named label")); + } + ProgramDefinition::Mapping => match self.mappings.get(identifier) { + Some(mapping) => { + // Write the variant. + 0u8.write_le(&mut writer)?; + // Write the mapping. + mapping.write_le(&mut writer)?; + } + None => return Err(error(format!("Mapping '{identifier}' is not defined"))), + }, + ProgramDefinition::Struct => match self.structs.get(identifier) { + Some(struct_) => { + // Write the variant. + 1u8.write_le(&mut writer)?; + // Write the struct. + struct_.write_le(&mut writer)?; + } + None => return Err(error(format!("Struct '{identifier}' is not defined."))), + }, + ProgramDefinition::Record => match self.records.get(identifier) { + Some(record) => { + // Write the variant. + 2u8.write_le(&mut writer)?; + // Write the record. + record.write_le(&mut writer)?; + } + None => return Err(error(format!("Record '{identifier}' is not defined."))), + }, + ProgramDefinition::Closure => match self.closures.get(identifier) { + Some(closure) => { + // Write the variant. + 3u8.write_le(&mut writer)?; + // Write the closure. + closure.write_le(&mut writer)?; + } + None => return Err(error(format!("Closure '{identifier}' is not defined."))), + }, + ProgramDefinition::Function => match self.functions.get(identifier) { + Some(function) => { + // Write the variant. + 4u8.write_le(&mut writer)?; + // Write the function. + function.write_le(&mut writer)?; + } + None => return Err(error(format!("Function '{identifier}' is not defined."))), + }, } - None => return Err(error(format!("Struct '{identifier}' is not defined."))), - }, - ProgramDefinition::Record => match self.records.get(identifier) { - Some(record) => { - // Write the variant. - 2u8.write_le(&mut writer)?; - // Write the record. - record.write_le(&mut writer)?; - } - None => return Err(error(format!("Record '{identifier}' is not defined."))), - }, - ProgramDefinition::Closure => match self.closures.get(identifier) { - Some(closure) => { - // Write the variant. - 3u8.write_le(&mut writer)?; - // Write the closure. - closure.write_le(&mut writer)?; - } - None => return Err(error(format!("Closure '{identifier}' is not defined."))), - }, - ProgramDefinition::Function => match self.functions.get(identifier) { - Some(function) => { - // Write the variant. - 4u8.write_le(&mut writer)?; - // Write the function. - function.write_le(&mut writer)?; - } - None => return Err(error(format!("Function '{identifier}' is not defined."))), - }, + } } } - Ok(()) } } diff --git a/synthesizer/program/src/constructor/bytes.rs b/synthesizer/program/src/constructor/bytes.rs new file mode 100644 index 0000000000..86677807e5 --- /dev/null +++ b/synthesizer/program/src/constructor/bytes.rs @@ -0,0 +1,94 @@ +// 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::*; + +impl FromBytes for ConstructorCore { + /// Reads the constructor from a buffer. + #[inline] + fn read_le(mut reader: R) -> IoResult { + // Read the commands. + let num_commands = u16::read_le(&mut reader)?; + if num_commands.is_zero() { + return Err(error("Failed to deserialize constructor: needs at least one command")); + } + if num_commands > u16::try_from(N::MAX_COMMANDS).map_err(error)? { + return Err(error(format!("Failed to deserialize constructor: too many commands ({num_commands})"))); + } + let mut commands = Vec::with_capacity(num_commands as usize); + for _ in 0..num_commands { + commands.push(Command::read_le(&mut reader)?); + } + // Initialize a new constructor. + let mut constructor = Self { commands: Default::default(), num_writes: 0, positions: Default::default() }; + commands.into_iter().try_for_each(|command| constructor.add_command(command)).map_err(error)?; + + Ok(constructor) + } +} + +impl ToBytes for ConstructorCore { + /// Writes the constructor to a buffer. + #[inline] + fn write_le(&self, mut writer: W) -> IoResult<()> { + // Write the number of commands for the constructor. + let num_commands = self.commands.len(); + match 0 < num_commands && num_commands <= N::MAX_COMMANDS { + true => u16::try_from(num_commands).map_err(error)?.write_le(&mut writer)?, + false => return Err(error(format!("Failed to write {num_commands} commands as bytes"))), + } + // Write the commands. + for command in self.commands.iter() { + command.write_le(&mut writer)?; + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::Constructor; + use console::network::MainnetV0; + + type CurrentNetwork = MainnetV0; + + #[test] + fn test_constructor_bytes() -> Result<()> { + let constructor_string = r" +constructor: + add r0 r1 into r2; + add r0 r1 into r3; + add r0 r1 into r4; + add r0 r1 into r5; + add r0 r1 into r6; + add r0 r1 into r7; + add r0 r1 into r8; + add r0 r1 into r9; + add r0 r1 into r10; + add r0 r1 into r11; + get accounts[r0] into r12; + get accounts[r1] into r13;"; + + let expected = Constructor::::from_str(constructor_string)?; + let expected_bytes = expected.to_bytes_le()?; + println!("String size: {:?}, Bytecode size: {:?}", constructor_string.len(), expected_bytes.len()); + + let candidate = Constructor::::from_bytes_le(&expected_bytes)?; + assert_eq!(expected.to_string(), candidate.to_string()); + assert_eq!(expected_bytes, candidate.to_bytes_le()?); + Ok(()) + } +} diff --git a/synthesizer/program/src/constructor/mod.rs b/synthesizer/program/src/constructor/mod.rs new file mode 100644 index 0000000000..76a3565b46 --- /dev/null +++ b/synthesizer/program/src/constructor/mod.rs @@ -0,0 +1,210 @@ +// 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. + +mod bytes; +mod parse; + +use crate::Command; + +use console::{ + network::prelude::*, + program::{Identifier, Register}, +}; + +use std::collections::HashMap; + +#[derive(Clone, PartialEq, Eq)] +pub struct ConstructorCore { + /// The commands, in order of execution. + commands: Vec>, + /// The number of write commands. + num_writes: u16, + /// A mapping from `Position`s to their index in `commands`. + positions: HashMap, usize>, +} + +impl ConstructorCore { + /// Returns the constructor commands. + pub fn commands(&self) -> &[Command] { + &self.commands + } + + /// Returns the number of write commands. + pub const fn num_writes(&self) -> u16 { + self.num_writes + } + + /// Returns the mapping of `Position`s to their index in `commands`. + pub const fn positions(&self) -> &HashMap, usize> { + &self.positions + } +} + +impl ConstructorCore { + /// Adds the given command to constructor. + /// + /// # Errors + /// This method will halt if the maximum number of commands has been reached. + #[inline] + pub fn add_command(&mut self, command: Command) -> Result<()> { + // Ensure the maximum number of commands has not been exceeded. + ensure!(self.commands.len() < N::MAX_COMMANDS, "Cannot add more than {} commands", N::MAX_COMMANDS); + // Ensure the number of write commands has not been exceeded. + if command.is_write() { + ensure!( + self.num_writes < N::MAX_WRITES, + "Cannot add more than {} 'set' & 'remove' commands", + N::MAX_WRITES + ); + } + + // Ensure the command is not an async instruction. + ensure!(!command.is_async(), "Forbidden operation: Constructor cannot invoke an 'async' instruction"); + // Ensure the command is not a call instruction. + ensure!(!command.is_call(), "Forbidden operation: Constructor cannot invoke a 'call'"); + // Ensure the command is not a cast to record instruction. + ensure!(!command.is_cast_to_record(), "Forbidden operation: Constructor cannot cast to a record"); + // Ensure the command is not an await command. + ensure!(!command.is_await(), "Forbidden operation: Constructor cannot 'await'"); + + // Check the destination registers. + for register in command.destinations() { + // Ensure the destination register is a locator. + ensure!(matches!(register, Register::Locator(..)), "Destination register must be a locator"); + } + + // Check if the command is a branch command. + if let Some(position) = command.branch_to() { + // Ensure the branch target does not reference an earlier position. + ensure!(!self.positions.contains_key(position), "Cannot branch to an earlier position '{position}'"); + } + + // Check if the command is a position command. + if let Some(position) = command.position() { + // Ensure the position is not yet defined. + ensure!(!self.positions.contains_key(position), "Cannot redefine position '{position}'"); + // Ensure that there are less than `u8::MAX` positions. + ensure!(self.positions.len() < N::MAX_POSITIONS, "Cannot add more than {} positions", N::MAX_POSITIONS); + // Insert the position. + self.positions.insert(*position, self.commands.len()); + } + + // Check if the command is a write command. + if command.is_write() { + // Increment the number of write commands. + self.num_writes += 1; + } + + // Insert the command. + self.commands.push(command); + Ok(()) + } +} + +impl TypeName for ConstructorCore { + /// Returns the type name as a string. + #[inline] + fn type_name() -> &'static str { + "constructor" + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::{Command, Constructor}; + + type CurrentNetwork = console::network::MainnetV0; + + #[test] + fn test_add_command() { + // Initialize a new constructor instance. + let mut constructor = Constructor:: { + commands: Default::default(), + num_writes: 0, + positions: Default::default(), + }; + + // Ensure that a command can be added. + let command = Command::::from_str("add r0 r1 into r2;").unwrap(); + assert!(constructor.add_command(command).is_ok()); + + // Ensure that adding more than the maximum number of commands will fail. + for i in 3..CurrentNetwork::MAX_COMMANDS * 2 { + let command = Command::::from_str(&format!("add r0 r1 into r{i};")).unwrap(); + + match constructor.commands.len() < CurrentNetwork::MAX_COMMANDS { + true => assert!(constructor.add_command(command).is_ok()), + false => assert!(constructor.add_command(command).is_err()), + } + } + + // Ensure that adding more than the maximum number of writes will fail. + + // Initialize a new constructor instance. + let mut constructor = Constructor:: { + commands: Default::default(), + num_writes: 0, + positions: Default::default(), + }; + + for _ in 0..CurrentNetwork::MAX_WRITES * 2 { + let command = Command::::from_str("remove object[r0];").unwrap(); + + match constructor.commands.len() < CurrentNetwork::MAX_WRITES as usize { + true => assert!(constructor.add_command(command).is_ok()), + false => assert!(constructor.add_command(command).is_err()), + } + } + } + + #[test] + fn test_add_command_duplicate_positions() { + // Initialize a new constructor instance. + let mut constructor = + Constructor { commands: Default::default(), num_writes: 0, positions: Default::default() }; + + // Ensure that a command can be added. + let command = Command::::from_str("position start;").unwrap(); + assert!(constructor.add_command(command.clone()).is_ok()); + + // Ensure that adding a duplicate position will fail. + assert!(constructor.add_command(command).is_err()); + + // Helper method to convert a number to a unique string. + #[allow(clippy::cast_possible_truncation)] + fn to_unique_string(mut n: usize) -> String { + let mut s = String::new(); + while n > 0 { + s.push((b'A' + (n % 26) as u8) as char); + n /= 26; + } + s.chars().rev().collect::() + } + + // Ensure that adding more than the maximum number of positions will fail. + for i in 1..u8::MAX as usize * 2 { + let position = to_unique_string(i); + println!("position: {position}"); + let command = Command::::from_str(&format!("position {position};")).unwrap(); + + match constructor.commands.len() < u8::MAX as usize { + true => assert!(constructor.add_command(command).is_ok()), + false => assert!(constructor.add_command(command).is_err()), + } + } + } +} diff --git a/synthesizer/program/src/constructor/parse.rs b/synthesizer/program/src/constructor/parse.rs new file mode 100644 index 0000000000..ce6ba30695 --- /dev/null +++ b/synthesizer/program/src/constructor/parse.rs @@ -0,0 +1,141 @@ +// 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::*; + +impl Parser for ConstructorCore { + /// Parses a string into constructor. + #[inline] + fn parse(string: &str) -> ParserResult { + // Parse the whitespace and comments from the string. + let (string, _) = Sanitizer::parse(string)?; + // Parse the 'constructor' keyword from the string. + let (string, _) = tag(Self::type_name())(string)?; + // Parse the whitespace from the string. + let (string, _) = Sanitizer::parse_whitespaces(string)?; + // Parse the colon ':' keyword from the string. + let (string, _) = tag(":")(string)?; + + // Parse the commands from the string. + let (string, commands) = many1(Command::parse)(string)?; + + map_res(take(0usize), move |_| { + // Initialize a new constructor. + let mut constructor = Self { commands: Default::default(), num_writes: 0, positions: Default::default() }; + if let Err(error) = commands.iter().cloned().try_for_each(|command| constructor.add_command(command)) { + eprintln!("{error}"); + return Err(error); + } + Ok::<_, Error>(constructor) + })(string) + } +} + +impl FromStr for ConstructorCore { + type Err = Error; + + /// Returns a constructor from a string literal. + fn from_str(string: &str) -> Result { + match Self::parse(string) { + Ok((remainder, object)) => { + // Ensure the remainder is empty. + ensure!(remainder.is_empty(), "Failed to parse string. Found invalid character in: \"{remainder}\""); + // Return the object. + Ok(object) + } + Err(error) => bail!("Failed to parse string. {error}"), + } + } +} + +impl Debug for ConstructorCore { + /// Prints the constructor as a string. + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + Display::fmt(self, f) + } +} + +impl Display for ConstructorCore { + /// Prints the constructor as a string. + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + // Write the constructor to a string. + write!(f, "{}:", Self::type_name())?; + self.commands.iter().try_for_each(|command| write!(f, "\n {command}")) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::Constructor; + use console::network::MainnetV0; + + type CurrentNetwork = MainnetV0; + + #[test] + fn test_constructor_parse() { + let constructor = Constructor::::parse( + r" +constructor: + add r0 r1 into r2;", + ) + .unwrap() + .1; + assert_eq!(1, constructor.commands().len()); + + // Constructor with 0 inputs. + let constructor = Constructor::::parse( + r" +constructor: + add 1u32 2u32 into r0;", + ) + .unwrap() + .1; + assert_eq!(1, constructor.commands().len()); + } + + #[test] + fn test_constructor_parse_cast() { + let constructor = Constructor::::parse( + r" +constructor: + cast 1u8 2u8 into r1 as token;", + ) + .unwrap() + .1; + assert_eq!(1, constructor.commands().len()); + } + + #[test] + fn test_constructor_display() { + let expected = r"constructor: + add r0 r1 into r2;"; + let constructor = Constructor::::parse(expected).unwrap().1; + assert_eq!(expected, format!("{constructor}"),); + } + + #[test] + fn test_empty_constructor() { + // Test that parsing an empty constructor fails. + assert!(Constructor::::parse("constructor:").is_err()); + // Test that attempting to serialize an empty constructor fails. + let constructor = Constructor:: { + commands: Default::default(), + num_writes: 0, + positions: Default::default(), + }; + assert!(constructor.write_le(Vec::new()).is_err()); + } +} diff --git a/synthesizer/program/src/finalize/mod.rs b/synthesizer/program/src/finalize/mod.rs index e7b4999dde..faa578a759 100644 --- a/synthesizer/program/src/finalize/mod.rs +++ b/synthesizer/program/src/finalize/mod.rs @@ -115,8 +115,16 @@ impl FinalizeCore { // Ensure the maximum number of commands has not been exceeded. ensure!(self.commands.len() < N::MAX_COMMANDS, "Cannot add more than {} commands", N::MAX_COMMANDS); // Ensure the number of write commands has not been exceeded. - ensure!(self.num_writes < N::MAX_WRITES, "Cannot add more than {} 'set' & 'remove' commands", N::MAX_WRITES); + if command.is_write() { + ensure!( + self.num_writes < N::MAX_WRITES, + "Cannot add more than {} 'set' & 'remove' commands", + N::MAX_WRITES + ); + } + // Ensure the command is not an async instruction. + ensure!(!command.is_async(), "Forbidden operation: Finalize cannot invoke an 'async' instruction"); // Ensure the command is not a call instruction. ensure!(!command.is_call(), "Forbidden operation: Finalize cannot invoke a 'call'"); // Ensure the command is not a cast to record instruction. @@ -139,7 +147,7 @@ impl FinalizeCore { // Ensure the position is not yet defined. ensure!(!self.positions.contains_key(position), "Cannot redefine position '{position}'"); // Ensure that there are less than `u8::MAX` positions. - ensure!(self.positions.len() < u8::MAX as usize, "Cannot add more than {} positions", u8::MAX); + ensure!(self.positions.len() < N::MAX_POSITIONS, "Cannot add more than {} positions", N::MAX_POSITIONS); // Insert the position. self.positions.insert(*position, self.commands.len()); } diff --git a/synthesizer/program/src/lib.rs b/synthesizer/program/src/lib.rs index c99dfa8d29..bc2714cd18 100644 --- a/synthesizer/program/src/lib.rs +++ b/synthesizer/program/src/lib.rs @@ -24,10 +24,14 @@ pub type Program = crate::ProgramCore; pub type Function = crate::FunctionCore; pub type Finalize = crate::FinalizeCore; pub type Closure = crate::ClosureCore; +pub type Constructor = crate::ConstructorCore; mod closure; pub use closure::*; +mod constructor; +pub use constructor::*; + pub mod finalize; pub use finalize::*; @@ -49,6 +53,7 @@ pub use traits::*; mod bytes; mod parse; mod serialize; +mod to_checksum; use console::{ network::{ @@ -66,6 +71,7 @@ use console::{ FromBytesDeserializer, FromStr, IoResult, + Itertools, Network, Parser, ParserResult, @@ -94,18 +100,29 @@ use console::{ }, }, program::{Identifier, PlaintextType, ProgramID, RecordType, StructType}, + types::U8, }; use snarkvm_utilities::cfg_iter; -use console::prelude::Itertools; use indexmap::{IndexMap, IndexSet}; use std::collections::BTreeSet; +use tiny_keccak::{Hasher, Sha3 as TinySha3}; + +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +enum ProgramLabel { + /// A program constructor. + Constructor, + /// A named component. + Identifier(Identifier), +} #[cfg(not(feature = "serial"))] use rayon::prelude::*; #[derive(Copy, Clone, PartialEq, Eq, Hash)] enum ProgramDefinition { + /// A program constructor. + Constructor, /// A program mapping. Mapping, /// A program struct. @@ -124,8 +141,10 @@ pub struct ProgramCore { id: ProgramID, /// A map of the declared imports for the program. imports: IndexMap, Import>, - /// A map of identifiers to their program declaration. - components: IndexMap, ProgramDefinition>, + /// A map of program labels to their program definitions. + components: IndexMap, ProgramDefinition>, + /// An optional constructor for the program. + constructor: Option>, /// A map of the declared mappings for the program. mappings: IndexMap, Mapping>, /// A map of the declared structs for the program. @@ -262,6 +281,7 @@ impl ProgramCore { Ok(Self { id, imports: IndexMap::new(), + constructor: None, components: IndexMap::new(), mappings: IndexMap::new(), structs: IndexMap::new(), @@ -287,6 +307,11 @@ impl ProgramCore { &self.imports } + /// Returns the constructor for the program. + pub const fn constructor(&self) -> Option<&ConstructorCore> { + self.constructor.as_ref() + } + /// Returns the mappings in the program. pub const fn mappings(&self) -> &IndexMap, Mapping> { &self.mappings @@ -317,6 +342,11 @@ impl ProgramCore { self.imports.contains_key(id) } + /// Returns `true` if the program contains a constructor. + pub const fn contains_constructor(&self) -> bool { + self.constructor.is_some() + } + /// Returns `true` if the program contains a mapping with the given name. pub fn contains_mapping(&self, name: &Identifier) -> bool { self.mappings.contains_key(name) @@ -446,6 +476,26 @@ impl ProgramCore { Ok(()) } + /// Adds a constructor to the program. + /// + /// # Errors + /// This method will halt if a constructor was previously added. + /// This method will halt if a constructor exceeds the maximum number of commands. + fn add_constructor(&mut self, constructor: ConstructorCore) -> Result<()> { + // Ensure the program does not already have a constructor. + ensure!(self.constructor.is_none(), "Program already has a constructor."); + // Ensure the number of commands is within the allowed range. + ensure!(!constructor.commands().is_empty(), "Constructor must have at least one command"); + ensure!(constructor.commands().len() <= N::MAX_COMMANDS, "Constructor exceeds maximum number of commands"); + // Add the constructor to the components. + if self.components.insert(ProgramLabel::Constructor, ProgramDefinition::Constructor).is_some() { + bail!("Constructor already exists in the program.") + } + // Add the constructor to the program. + self.constructor = Some(constructor); + Ok(()) + } + /// Adds a new mapping to the program. /// /// # Errors @@ -467,7 +517,7 @@ impl ProgramCore { ensure!(!Self::is_reserved_opcode(&mapping_name.to_string()), "'{mapping_name}' is a reserved opcode."); // Add the mapping name to the identifiers. - if self.components.insert(mapping_name, ProgramDefinition::Mapping).is_some() { + if self.components.insert(ProgramLabel::Identifier(mapping_name), ProgramDefinition::Mapping).is_some() { bail!("'{mapping_name}' already exists in the program.") } // Add the mapping to the program. @@ -528,7 +578,7 @@ impl ProgramCore { } // Add the struct name to the identifiers. - if self.components.insert(struct_name, ProgramDefinition::Struct).is_some() { + if self.components.insert(ProgramLabel::Identifier(struct_name), ProgramDefinition::Struct).is_some() { bail!("'{}' already exists in the program.", struct_name) } // Add the struct to the program. @@ -585,7 +635,7 @@ impl ProgramCore { } // Add the record name to the identifiers. - if self.components.insert(record_name, ProgramDefinition::Record).is_some() { + if self.components.insert(ProgramLabel::Identifier(record_name), ProgramDefinition::Record).is_some() { bail!("'{record_name}' already exists in the program.") } // Add the record to the program. @@ -633,7 +683,7 @@ impl ProgramCore { ensure!(closure.outputs().len() <= N::MAX_OUTPUTS, "Closure exceeds maximum number of outputs"); // Add the function name to the identifiers. - if self.components.insert(closure_name, ProgramDefinition::Closure).is_some() { + if self.components.insert(ProgramLabel::Identifier(closure_name), ProgramDefinition::Closure).is_some() { bail!("'{closure_name}' already exists in the program.") } // Add the closure to the program. @@ -679,7 +729,7 @@ impl ProgramCore { ensure!(function.outputs().len() <= N::MAX_OUTPUTS, "Function exceeds maximum number of outputs"); // Add the function name to the identifiers. - if self.components.insert(function_name, ProgramDefinition::Function).is_some() { + if self.components.insert(ProgramLabel::Identifier(function_name), ProgramDefinition::Function).is_some() { bail!("'{function_name}' already exists in the program.") } // Add the function to the program. @@ -691,7 +741,7 @@ impl ProgramCore { /// Returns `true` if the given name does not already exist in the program. fn is_unique_name(&self, name: &Identifier) -> bool { - !self.components.contains_key(name) + !self.components.contains_key(&ProgramLabel::Identifier(*name)) } /// Returns `true` if the given name is a reserved opcode. @@ -731,9 +781,16 @@ impl ProgramCore { bail!("Program name '{program_name}' is a restricted keyword for the current consensus version") } // Check that all top-level program components are not restricted keywords. - for identifier in self.components.keys() { - if keywords.contains(identifier.to_string().as_str()) { - bail!("Program component '{identifier}' is a restricted keyword for the current consensus version") + for component in self.components.keys() { + match component { + ProgramLabel::Identifier(identifier) => { + if keywords.contains(identifier.to_string().as_str()) { + bail!( + "Program component '{identifier}' is a restricted keyword for the current consensus version" + ) + } + } + ProgramLabel::Constructor => continue, } } // Check that all record entry names are not restricted keywords. @@ -827,6 +884,34 @@ impl ProgramCore { })?; Ok(()) } + + /// Returns `true` if a program contains any V9 syntax. + /// This includes `constructor`, `Operand::Edition`, `Operand::Checksum`, and `Operand::ProgramOwner`. + /// This is enforced to be `false` for programs before `ConsensusVersion::V9`. + #[inline] + pub fn contains_v9_syntax(&self) -> bool { + // Check if the program contains a constructor. + if self.contains_constructor() { + return true; + } + // Check each instruction and output in each function's finalize scope for the use of + // `Operand::Checksum`, `Operand::Edition` or `Operand::ProgramOwner`. + for function in self.functions().values() { + // Check the finalize scope if it exists. + if let Some(finalize_logic) = function.finalize_logic() { + // Check the command operands. + for command in finalize_logic.commands() { + for operand in command.operands() { + if matches!(operand, Operand::Checksum(_) | Operand::Edition(_) | Operand::ProgramOwner(_)) { + return true; + } + } + } + } + } + // Return `false` since no V9 syntax was found. + false + } } impl TypeName for ProgramCore { @@ -1010,4 +1095,83 @@ function swap: Ok(()) } + + #[test] + fn test_program_with_constructor() { + // Initialize a new program. + let program_string = r"import credits.aleo; + +program good_constructor.aleo; + +constructor: + assert.eq edition 0u16; + assert.eq credits.aleo/edition 0u16; + assert.neq checksum 0field; + assert.eq credits.aleo/checksum 6192738754253668739186185034243585975029374333074931926190215457304721124008field; + set 1u8 into data[0u8]; + +mapping data: + key as u8.public; + value as u8.public; + +function dummy: + +function check: + async check into r0; + output r0 as good_constructor.aleo/check.future; + +finalize check: + get data[0u8] into r0; + assert.eq r0 1u8; +"; + let program = Program::::from_str(program_string).unwrap(); + + // Check that the string and bytes (de)serialization works. + let serialized = program.to_string(); + let deserialized = Program::::from_str(&serialized).unwrap(); + assert_eq!(program, deserialized); + + let serialized = program.to_bytes_le().unwrap(); + let deserialized = Program::::from_bytes_le(&serialized).unwrap(); + assert_eq!(program, deserialized); + + // Check that the display works. + let display = format!("{program}"); + assert_eq!(display, program_string); + + // Ensure the program contains a constructor. + assert!(program.contains_constructor()); + assert_eq!(program.constructor().unwrap().commands().len(), 5); + } + + #[test] + fn test_program_equality_and_checksum() { + fn run_test(program1: &str, program2: &str, expected_equal: bool) { + println!("Comparing programs:\n{program1}\n{program2}"); + let program1 = Program::::from_str(program1).unwrap(); + let program2 = Program::::from_str(program2).unwrap(); + assert_eq!(program1 == program2, expected_equal); + assert_eq!(program1.to_checksum() == program2.to_checksum(), expected_equal); + } + + // Test two identical programs, with different whitespace. + run_test(r"program test.aleo; function dummy: ", r"program test.aleo; function dummy: ", true); + + // Test two programs, one with a different function name. + run_test(r"program test.aleo; function dummy: ", r"program test.aleo; function bummy: ", false); + + // Test two programs, one with a constructor and one without. + run_test( + r"program test.aleo; function dummy: ", + r"program test.aleo; constructor: assert.eq true true; function dummy: ", + false, + ); + + // Test two programs, both with a struct and function, but in different order. + run_test( + r"program test.aleo; struct foo: data as u8; function dummy:", + r"program test.aleo; function dummy: struct foo: data as u8;", + false, + ); + } } diff --git a/synthesizer/program/src/logic/command/await_.rs b/synthesizer/program/src/logic/command/await_.rs index 3c4a797880..696d24d9ee 100644 --- a/synthesizer/program/src/logic/command/await_.rs +++ b/synthesizer/program/src/logic/command/await_.rs @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::Opcode; +use crate::{Opcode, Operand}; use console::{network::prelude::*, program::Register}; /// An await command, e.g. `await r0;`. @@ -21,8 +21,9 @@ use console::{network::prelude::*, program::Register}; /// Note that asynchronous calls currently do not return a value. #[derive(Clone, PartialEq, Eq, Hash)] pub struct Await { - /// The register containing the future. - register: Register, + /// The operands. + /// Note: Byte and string parsing guarantees that this is a single register operand. + operands: [Operand; 1], } impl Await { @@ -32,10 +33,21 @@ impl Await { Opcode::Command("await") } + /// Returns the operands in the command. + #[inline] + pub fn operands(&self) -> &[Operand] { + &self.operands + } + /// Returns the register containing the future. #[inline] - pub const fn register(&self) -> &Register { - &self.register + pub fn register(&self) -> &Register { + // Note: This byte and string parsing guarantees that the operand is a single register. + let Operand::Register(register) = &self.operands[0] else { + unreachable!("The operands of an await command must be a single register.") + }; + // Return the register. + register } } @@ -56,7 +68,7 @@ impl Parser for Await { // Parse the ';' from the string. let (string, _) = tag(";")(string)?; - Ok((string, Self { register })) + Ok((string, Self { operands: [Operand::Register(register)] })) } } @@ -89,7 +101,7 @@ impl Display for Await { /// Prints the command to a string. fn fmt(&self, f: &mut Formatter) -> fmt::Result { // Print the command. - write!(f, "await {};", self.register) + write!(f, "await {};", self.register()) } } @@ -99,7 +111,7 @@ impl FromBytes for Await { // Read the register. let register = Register::read_le(&mut reader)?; - Ok(Self { register }) + Ok(Self { operands: [Operand::Register(register)] }) } } @@ -107,7 +119,7 @@ impl ToBytes for Await { /// Writes the operation to a buffer. fn write_le(&self, mut writer: W) -> IoResult<()> { // Write the register. - self.register.write_le(&mut writer)?; + self.register().write_le(&mut writer)?; Ok(()) } } diff --git a/synthesizer/program/src/logic/command/branch.rs b/synthesizer/program/src/logic/command/branch.rs index 7ed7258030..f02bd5e497 100644 --- a/synthesizer/program/src/logic/command/branch.rs +++ b/synthesizer/program/src/logic/command/branch.rs @@ -29,10 +29,8 @@ enum Variant { /// Compares `first` and `second` and jumps to `position`, if the condition is met. #[derive(Clone, PartialEq, Eq, Hash)] pub struct Branch { - /// The first operand. - first: Operand, - /// The second operand. - second: Operand, + /// The operands. + operands: [Operand; 2], /// The position. position: Identifier, } @@ -48,21 +46,27 @@ impl Branch { } } + /// Returns the operands. + #[inline] + pub const fn operands(&self) -> &[Operand] { + &self.operands + } + /// Returns the first operand. #[inline] - pub fn first(&self) -> &Operand { - &self.first + pub const fn first(&self) -> &Operand { + &self.operands[0] } /// Returns the second operand. #[inline] - pub fn second(&self) -> &Operand { - &self.second + pub const fn second(&self) -> &Operand { + &self.operands[1] } /// Returns the position. #[inline] - pub fn position(&self) -> &Identifier { + pub const fn position(&self) -> &Identifier { &self.position } } @@ -100,7 +104,7 @@ impl Parser for Branch { // Parse the ";" from the string. let (string, _) = tag(";")(string)?; - Ok((string, Self { first, second, position })) + Ok((string, Self { operands: [first, second], position })) } } @@ -133,7 +137,7 @@ impl Display for Branch { /// Prints the command to a string. fn fmt(&self, f: &mut Formatter) -> fmt::Result { // Print the command. - write!(f, "{} {} {} to {};", Self::opcode(), self.first, self.second, self.position) + write!(f, "{} {} {} to {};", Self::opcode(), self.first(), self.second(), self.position) } } @@ -148,7 +152,7 @@ impl FromBytes for Branch { let position = Identifier::read_le(&mut reader)?; // Return the command. - Ok(Self { first, second, position }) + Ok(Self { operands: [first, second], position }) } } @@ -156,9 +160,9 @@ impl ToBytes for Branch { /// Writes the command to a buffer. fn write_le(&self, mut writer: W) -> IoResult<()> { // Write the first operand. - self.first.write_le(&mut writer)?; + self.first().write_le(&mut writer)?; // Write the second operand. - self.second.write_le(&mut writer)?; + self.second().write_le(&mut writer)?; // Write the position. self.position.write_le(&mut writer) } @@ -178,14 +182,14 @@ mod tests { fn test_parse() { let (string, branch) = BranchEq::::parse("branch.eq r0 r1 to exit;").unwrap(); assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'"); - assert_eq!(branch.first, Operand::Register(Register::Locator(0)), "The first operand is incorrect"); - assert_eq!(branch.second, Operand::Register(Register::Locator(1)), "The second operand is incorrect"); + assert_eq!(branch.first(), &Operand::Register(Register::Locator(0)), "The first operand is incorrect"); + assert_eq!(branch.second(), &Operand::Register(Register::Locator(1)), "The second operand is incorrect"); assert_eq!(branch.position, Identifier::from_str("exit").unwrap(), "The position is incorrect"); let (string, branch) = BranchNeq::::parse("branch.neq r3 r4 to start;").unwrap(); assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'"); - assert_eq!(branch.first, Operand::Register(Register::Locator(3)), "The first operand is incorrect"); - assert_eq!(branch.second, Operand::Register(Register::Locator(4)), "The second operand is incorrect"); + assert_eq!(branch.first(), &Operand::Register(Register::Locator(3)), "The first operand is incorrect"); + assert_eq!(branch.second(), &Operand::Register(Register::Locator(4)), "The second operand is incorrect"); assert_eq!(branch.position, Identifier::from_str("start").unwrap(), "The position is incorrect"); } } diff --git a/synthesizer/program/src/logic/command/contains.rs b/synthesizer/program/src/logic/command/contains.rs index e5c8882fa5..5ffd8d92e9 100644 --- a/synthesizer/program/src/logic/command/contains.rs +++ b/synthesizer/program/src/logic/command/contains.rs @@ -26,8 +26,8 @@ use console::{ pub struct Contains { /// The mapping name. mapping: CallOperator, - /// The key to access the mapping. - key: Operand, + /// The operands. + operands: [Operand; 1], /// The destination register. destination: Register, } @@ -41,8 +41,8 @@ impl Contains { /// Returns the operands in the operation. #[inline] - pub fn operands(&self) -> Vec> { - vec![self.key.clone()] + pub fn operands(&self) -> &[Operand] { + &self.operands } /// Returns the mapping. @@ -54,7 +54,7 @@ impl Contains { /// Returns the operand containing the key. #[inline] pub const fn key(&self) -> &Operand { - &self.key + &self.operands[0] } /// Returns the destination register. @@ -78,13 +78,13 @@ impl Contains { CallOperator::Resource(mapping_name) => (*stack.program_id(), mapping_name), }; - // Ensure the mapping exists in storage. - if !store.contains_mapping_confirmed(&program_id, &mapping_name)? { - bail!("Mapping '{program_id}/{mapping_name}' does not exist in storage"); + // Ensure the mapping exists. + if !store.contains_mapping_speculative(&program_id, &mapping_name)? { + bail!("Mapping '{program_id}/{mapping_name}' does not exist"); } // Load the operand as a plaintext. - let key = registers.load_plaintext(stack, &self.key)?; + let key = registers.load_plaintext(stack, self.key())?; // Determine if the key exists in the mapping. let contains_key = store.contains_key_speculative(program_id, mapping_name, &key)?; @@ -133,7 +133,7 @@ impl Parser for Contains { // Parse the ";" from the string. let (string, _) = tag(";")(string)?; - Ok((string, Self { mapping, key, destination })) + Ok((string, Self { mapping, operands: [key], destination })) } } @@ -168,7 +168,7 @@ impl Display for Contains { // Print the command. write!(f, "{} ", Self::opcode())?; // Print the mapping and key operand. - write!(f, "{}[{}] into ", self.mapping, self.key)?; + write!(f, "{}[{}] into ", self.mapping, self.key())?; // Print the destination register. write!(f, "{};", self.destination) } @@ -184,7 +184,7 @@ impl FromBytes for Contains { // Read the destination register. let destination = Register::read_le(&mut reader)?; // Return the command. - Ok(Self { mapping, key, destination }) + Ok(Self { mapping, operands: [key], destination }) } } @@ -194,7 +194,7 @@ impl ToBytes for Contains { // Write the mapping name. self.mapping.write_le(&mut writer)?; // Write the key operand. - self.key.write_le(&mut writer)?; + self.key().write_le(&mut writer)?; // Write the destination register. self.destination.write_le(&mut writer) } @@ -213,7 +213,7 @@ mod tests { assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'"); assert_eq!(contains.mapping, CallOperator::from_str("account").unwrap()); assert_eq!(contains.operands().len(), 1, "The number of operands is incorrect"); - assert_eq!(contains.key, Operand::Register(Register::Locator(0)), "The first operand is incorrect"); + assert_eq!(contains.key(), &Operand::Register(Register::Locator(0)), "The first operand is incorrect"); assert_eq!(contains.destination, Register::Locator(1), "The second operand is incorrect"); let (string, contains) = @@ -221,7 +221,7 @@ mod tests { assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'"); assert_eq!(contains.mapping, CallOperator::from_str("credits.aleo/account").unwrap()); assert_eq!(contains.operands().len(), 1, "The number of operands is incorrect"); - assert_eq!(contains.key, Operand::Register(Register::Locator(0)), "The first operand is incorrect"); + assert_eq!(contains.key(), &Operand::Register(Register::Locator(0)), "The first operand is incorrect"); assert_eq!(contains.destination, Register::Locator(1), "The second operand is incorrect"); } diff --git a/synthesizer/program/src/logic/command/get.rs b/synthesizer/program/src/logic/command/get.rs index 456de1c6b6..03ab485c28 100644 --- a/synthesizer/program/src/logic/command/get.rs +++ b/synthesizer/program/src/logic/command/get.rs @@ -21,36 +21,16 @@ use console::{ /// A get command, e.g. `get accounts[r0] into r1;`. /// Gets the value stored at `operand` in `mapping` and stores the result in `destination`. -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq, Hash)] pub struct Get { /// The mapping. mapping: CallOperator, - /// The key to access the mapping. - key: Operand, + /// The operands. + operands: [Operand; 1], /// The destination register. destination: Register, } -impl PartialEq for Get { - /// Returns true if the two objects are equal. - #[inline] - fn eq(&self, other: &Self) -> bool { - self.mapping == other.mapping && self.key == other.key && self.destination == other.destination - } -} - -impl Eq for Get {} - -impl std::hash::Hash for Get { - /// Returns the hash of the object. - #[inline] - fn hash(&self, state: &mut H) { - self.mapping.hash(state); - self.key.hash(state); - self.destination.hash(state); - } -} - impl Get { /// Returns the opcode. #[inline] @@ -60,8 +40,8 @@ impl Get { /// Returns the operands in the operation. #[inline] - pub fn operands(&self) -> Vec> { - vec![self.key.clone()] + pub fn operands(&self) -> &[Operand] { + &self.operands } /// Returns the mapping. @@ -73,7 +53,7 @@ impl Get { /// Returns the operand containing the key. #[inline] pub const fn key(&self) -> &Operand { - &self.key + &self.operands[0] } /// Returns the destination register. @@ -98,13 +78,13 @@ impl Get { CallOperator::Resource(mapping_name) => (*stack.program_id(), mapping_name), }; - // Ensure the mapping exists in storage. - if !store.contains_mapping_confirmed(&program_id, &mapping_name)? { - bail!("Mapping '{program_id}/{mapping_name}' does not exist in storage"); + // Ensure the mapping exists. + if !store.contains_mapping_speculative(&program_id, &mapping_name)? { + bail!("Mapping '{program_id}/{mapping_name}' does not exist"); } // Load the operand as a plaintext. - let key = registers.load_plaintext(stack, &self.key)?; + let key = registers.load_plaintext(stack, self.key())?; // Retrieve the value from storage as a literal. let value = match store.get_value_speculative(program_id, mapping_name, &key)? { @@ -160,7 +140,7 @@ impl Parser for Get { // Parse the ";" from the string. let (string, _) = tag(";")(string)?; - Ok((string, Self { mapping, key, destination })) + Ok((string, Self { mapping, operands: [key], destination })) } } @@ -195,7 +175,7 @@ impl Display for Get { // Print the command. write!(f, "{} ", Self::opcode())?; // Print the mapping and key operand. - write!(f, "{}[{}] into ", self.mapping, self.key)?; + write!(f, "{}[{}] into ", self.mapping, self.key())?; // Print the destination register. write!(f, "{};", self.destination) } @@ -211,7 +191,7 @@ impl FromBytes for Get { // Read the destination register. let destination = Register::read_le(&mut reader)?; // Return the command. - Ok(Self { mapping, key, destination }) + Ok(Self { mapping, operands: [key], destination }) } } @@ -221,7 +201,7 @@ impl ToBytes for Get { // Write the mapping name. self.mapping.write_le(&mut writer)?; // Write the key operand. - self.key.write_le(&mut writer)?; + self.key().write_le(&mut writer)?; // Write the destination register. self.destination.write_le(&mut writer) } @@ -240,14 +220,14 @@ mod tests { assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'"); assert_eq!(get.mapping, CallOperator::from_str("account").unwrap()); assert_eq!(get.operands().len(), 1, "The number of operands is incorrect"); - assert_eq!(get.key, Operand::Register(Register::Locator(0)), "The first operand is incorrect"); + assert_eq!(get.key(), &Operand::Register(Register::Locator(0)), "The first operand is incorrect"); assert_eq!(get.destination, Register::Locator(1), "The second operand is incorrect"); let (string, get) = Get::::parse("get token.aleo/balances[r0] into r1;").unwrap(); assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'"); assert_eq!(get.mapping, CallOperator::from_str("token.aleo/balances").unwrap()); assert_eq!(get.operands().len(), 1, "The number of operands is incorrect"); - assert_eq!(get.key, Operand::Register(Register::Locator(0)), "The first operand is incorrect"); + assert_eq!(get.key(), &Operand::Register(Register::Locator(0)), "The first operand is incorrect"); assert_eq!(get.destination, Register::Locator(1), "The second operand is incorrect"); } diff --git a/synthesizer/program/src/logic/command/get_or_use.rs b/synthesizer/program/src/logic/command/get_or_use.rs index 68061711de..d4dade8231 100644 --- a/synthesizer/program/src/logic/command/get_or_use.rs +++ b/synthesizer/program/src/logic/command/get_or_use.rs @@ -22,40 +22,16 @@ use console::{ /// A get command that uses the provided default in case of failure, e.g. `get.or_use accounts[r0] r1 into r2;`. /// Gets the value stored at `operand` in `mapping` and stores the result in `destination`. /// If the key is not present, `default` is stored in `destination`. -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq, Hash)] pub struct GetOrUse { /// The mapping. mapping: CallOperator, - /// The key to access the mapping. - key: Operand, - /// The default value. - default: Operand, + /// The operands. + operands: [Operand; 2], /// The destination register. destination: Register, } -impl PartialEq for GetOrUse { - #[inline] - fn eq(&self, other: &Self) -> bool { - self.mapping == other.mapping - && self.key == other.key - && self.default == other.default - && self.destination == other.destination - } -} - -impl Eq for GetOrUse {} - -impl std::hash::Hash for GetOrUse { - #[inline] - fn hash(&self, state: &mut H) { - self.mapping.hash(state); - self.key.hash(state); - self.default.hash(state); - self.destination.hash(state); - } -} - impl GetOrUse { /// Returns the opcode. #[inline] @@ -65,8 +41,8 @@ impl GetOrUse { /// Returns the operands in the operation. #[inline] - pub fn operands(&self) -> Vec> { - vec![self.key.clone(), self.default.clone()] + pub fn operands(&self) -> &[Operand] { + &self.operands } /// Returns the mapping. @@ -78,13 +54,13 @@ impl GetOrUse { /// Returns the operand containing the key. #[inline] pub const fn key(&self) -> &Operand { - &self.key + &self.operands[0] } /// Returns the default value. #[inline] pub const fn default(&self) -> &Operand { - &self.default + &self.operands[1] } /// Returns the destination register. @@ -109,13 +85,13 @@ impl GetOrUse { CallOperator::Resource(mapping_name) => (*stack.program_id(), mapping_name), }; - // Ensure the mapping exists in storage. - if !store.contains_mapping_confirmed(&program_id, &mapping_name)? { - bail!("Mapping '{program_id}/{mapping_name}' does not exist in storage"); + // Ensure the mapping exists. + if !store.contains_mapping_speculative(&program_id, &mapping_name)? { + bail!("Mapping '{program_id}/{mapping_name}' does not exist"); } // Load the operand as a plaintext. - let key = registers.load_plaintext(stack, &self.key)?; + let key = registers.load_plaintext(stack, self.key())?; // Retrieve the value from storage as a literal. let value = match store.get_value_speculative(program_id, mapping_name, &key)? { @@ -123,7 +99,7 @@ impl GetOrUse { Some(Value::Record(..)) => bail!("Cannot 'get.or_use' a 'record'"), Some(Value::Future(..)) => bail!("Cannot 'get.or_use' a 'future'"), // If a key does not exist, then use the default value. - None => Value::Plaintext(registers.load_plaintext(stack, &self.default)?), + None => Value::Plaintext(registers.load_plaintext(stack, self.default())?), }; // Assign the value to the destination register. @@ -176,7 +152,7 @@ impl Parser for GetOrUse { // Parse the ";" from the string. let (string, _) = tag(";")(string)?; - Ok((string, Self { mapping, key, default, destination })) + Ok((string, Self { mapping, operands: [key, default], destination })) } } @@ -211,7 +187,7 @@ impl Display for GetOrUse { // Print the command. write!(f, "{} ", Self::opcode())?; // Print the mapping and key operand. - write!(f, "{}[{}] {} into ", self.mapping, self.key, self.default)?; + write!(f, "{}[{}] {} into ", self.mapping, self.key(), self.default())?; // Print the destination register. write!(f, "{};", self.destination) } @@ -229,7 +205,7 @@ impl FromBytes for GetOrUse { // Read the destination register. let destination = Register::read_le(&mut reader)?; // Return the command. - Ok(Self { mapping, key, default, destination }) + Ok(Self { mapping, operands: [key, default], destination }) } } @@ -239,9 +215,9 @@ impl ToBytes for GetOrUse { // Write the mapping name. self.mapping.write_le(&mut writer)?; // Write the key operand. - self.key.write_le(&mut writer)?; + self.key().write_le(&mut writer)?; // Write the default value. - self.default.write_le(&mut writer)?; + self.default().write_le(&mut writer)?; // Write the destination register. self.destination.write_le(&mut writer) } @@ -260,8 +236,8 @@ mod tests { assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'"); assert_eq!(get_or_use.mapping, CallOperator::from_str("account").unwrap()); assert_eq!(get_or_use.operands().len(), 2, "The number of operands is incorrect"); - assert_eq!(get_or_use.key, Operand::Register(Register::Locator(0)), "The first operand is incorrect"); - assert_eq!(get_or_use.default, Operand::Register(Register::Locator(1)), "The second operand is incorrect"); + assert_eq!(get_or_use.key(), &Operand::Register(Register::Locator(0)), "The first operand is incorrect"); + assert_eq!(get_or_use.default(), &Operand::Register(Register::Locator(1)), "The second operand is incorrect"); assert_eq!(get_or_use.destination, Register::Locator(2), "The second operand is incorrect"); let (string, get_or_use) = @@ -269,8 +245,8 @@ mod tests { assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'"); assert_eq!(get_or_use.mapping, CallOperator::from_str("token.aleo/balances").unwrap()); assert_eq!(get_or_use.operands().len(), 2, "The number of operands is incorrect"); - assert_eq!(get_or_use.key, Operand::Register(Register::Locator(0)), "The first operand is incorrect"); - assert_eq!(get_or_use.default, Operand::Register(Register::Locator(1)), "The second operand is incorrect"); + assert_eq!(get_or_use.key(), &Operand::Register(Register::Locator(0)), "The first operand is incorrect"); + assert_eq!(get_or_use.default(), &Operand::Register(Register::Locator(1)), "The second operand is incorrect"); assert_eq!(get_or_use.destination, Register::Locator(2), "The second operand is incorrect"); } diff --git a/synthesizer/program/src/logic/command/mod.rs b/synthesizer/program/src/logic/command/mod.rs index e46b7e4299..f6b341ff94 100644 --- a/synthesizer/program/src/logic/command/mod.rs +++ b/synthesizer/program/src/logic/command/mod.rs @@ -40,7 +40,15 @@ pub use position::*; mod set; pub use set::*; -use crate::{CastType, FinalizeOperation, FinalizeRegistersState, FinalizeStoreTrait, Instruction, StackTrait}; +use crate::{ + CastType, + FinalizeOperation, + FinalizeRegistersState, + FinalizeStoreTrait, + Instruction, + Operand, + StackTrait, +}; use console::{ network::prelude::*, program::{Identifier, Register}, @@ -74,21 +82,30 @@ pub enum Command { } impl Command { - /// Returns the destination registers of the command. - pub fn destinations(&self) -> Vec> { - match self { - Command::Instruction(instruction) => instruction.destinations(), - Command::Contains(contains) => vec![contains.destination().clone()], - Command::Get(get) => vec![get.destination().clone()], - Command::GetOrUse(get_or_use) => vec![get_or_use.destination().clone()], - Command::RandChaCha(rand_chacha) => vec![rand_chacha.destination().clone()], - Command::Await(_) - | Command::BranchEq(_) - | Command::BranchNeq(_) - | Command::Position(_) - | Command::Remove(_) - | Command::Set(_) => vec![], - } + /// Returns `true` if the command is an async instruction. + pub fn is_async(&self) -> bool { + matches!(self, Command::Instruction(Instruction::Async(_))) + } + + /// Returns `true` if the command is an await command. + #[inline] + pub fn is_await(&self) -> bool { + matches!(self, Command::Await(_)) + } + + /// Returns `true` if the command is a call instruction. + pub fn is_call(&self) -> bool { + matches!(self, Command::Instruction(Instruction::Call(_))) + } + + /// Returns `true` if the command is a cast to record instruction. + pub fn is_cast_to_record(&self) -> bool { + matches!(self, Command::Instruction(Instruction::Cast(cast)) if matches!(cast.cast_type(), CastType::Record(_) | CastType::ExternalRecord(_))) + } + + /// Returns `true` if the command is a write operation. + pub fn is_write(&self) -> bool { + matches!(self, Command::Set(_) | Command::Remove(_)) } /// Returns the branch target, if the command is a branch command. @@ -110,19 +127,39 @@ impl Command { } } - /// Returns `true` if the command is a call instruction. - pub fn is_call(&self) -> bool { - matches!(self, Command::Instruction(Instruction::Call(_))) - } - - /// Returns `true` if the command is a cast to record instruction. - pub fn is_cast_to_record(&self) -> bool { - matches!(self, Command::Instruction(Instruction::Cast(cast)) if matches!(cast.cast_type(), CastType::Record(_) | CastType::ExternalRecord(_))) + /// Returns the destination registers of the command. + pub fn destinations(&self) -> Vec> { + match self { + Command::Instruction(instruction) => instruction.destinations(), + Command::Contains(contains) => vec![contains.destination().clone()], + Command::Get(get) => vec![get.destination().clone()], + Command::GetOrUse(get_or_use) => vec![get_or_use.destination().clone()], + Command::RandChaCha(rand_chacha) => vec![rand_chacha.destination().clone()], + Command::Await(_) + | Command::BranchEq(_) + | Command::BranchNeq(_) + | Command::Position(_) + | Command::Remove(_) + | Command::Set(_) => vec![], + } } - /// Returns `true` if the command is a write operation. - pub fn is_write(&self) -> bool { - matches!(self, Command::Set(_) | Command::Remove(_)) + /// Returns the operands of the command. + #[inline] + pub fn operands(&self) -> &[Operand] { + match self { + Command::Instruction(c) => c.operands(), + Command::Await(c) => c.operands(), + Command::Contains(c) => c.operands(), + Command::Get(c) => c.operands(), + Command::GetOrUse(c) => c.operands(), + Command::RandChaCha(c) => c.operands(), + Command::Remove(c) => c.operands(), + Command::Set(c) => c.operands(), + Command::BranchEq(c) => c.operands(), + Command::BranchNeq(c) => c.operands(), + Command::Position(_) => Default::default(), + } } /// Finalizes the command. diff --git a/synthesizer/program/src/logic/command/rand_chacha.rs b/synthesizer/program/src/logic/command/rand_chacha.rs index 2f4e9c8710..5a172a420a 100644 --- a/synthesizer/program/src/logic/command/rand_chacha.rs +++ b/synthesizer/program/src/logic/command/rand_chacha.rs @@ -50,8 +50,8 @@ impl RandChaCha { /// Returns the operands in the operation. #[inline] - pub fn operands(&self) -> Vec> { - self.operands.clone() + pub fn operands(&self) -> &[Operand] { + &self.operands } /// Returns the destination register. diff --git a/synthesizer/program/src/logic/command/remove.rs b/synthesizer/program/src/logic/command/remove.rs index a17afa8597..3cefb33956 100644 --- a/synthesizer/program/src/logic/command/remove.rs +++ b/synthesizer/program/src/logic/command/remove.rs @@ -22,8 +22,8 @@ use console::{network::prelude::*, program::Identifier}; pub struct Remove { /// The mapping name. mapping: Identifier, - /// The key to access the mapping. - key: Operand, + /// The operands + operands: [Operand; 1], } impl Remove { @@ -35,8 +35,8 @@ impl Remove { /// Returns the operands in the operation. #[inline] - pub fn operands(&self) -> Vec> { - vec![self.key.clone()] + pub fn operands(&self) -> &[Operand] { + &self.operands } /// Returns the mapping name. @@ -48,7 +48,7 @@ impl Remove { /// Returns the operand containing the key. #[inline] pub const fn key(&self) -> &Operand { - &self.key + &self.operands[0] } } @@ -60,13 +60,13 @@ impl Remove { store: &impl FinalizeStoreTrait, registers: &mut impl RegistersTrait, ) -> Result>> { - // Ensure the mapping exists in storage. - if !store.contains_mapping_confirmed(stack.program_id(), &self.mapping)? { - bail!("Mapping '{}/{}' does not exist in storage", stack.program_id(), self.mapping); + // Ensure the mapping exists. + if !store.contains_mapping_speculative(stack.program_id(), &self.mapping)? { + bail!("Mapping '{}/{}' does not exist", stack.program_id(), self.mapping); } // Load the key operand as a plaintext. - let key = registers.load_plaintext(stack, &self.key)?; + let key = registers.load_plaintext(stack, self.key())?; // Update the value in storage, and return the finalize operation. store.remove_key_value(*stack.program_id(), self.mapping, &key) } @@ -99,7 +99,7 @@ impl Parser for Remove { // Parse the ";" from the string. let (string, _) = tag(";")(string)?; - Ok((string, Self { mapping, key })) + Ok((string, Self { mapping, operands: [key] })) } } @@ -131,7 +131,7 @@ impl Display for Remove { /// Prints the command to a string. fn fmt(&self, f: &mut Formatter) -> fmt::Result { // Print the command, mapping, and key operand. - write!(f, "{} {}[{}];", Self::opcode(), self.mapping, self.key) + write!(f, "{} {}[{}];", Self::opcode(), self.mapping, self.key()) } } @@ -143,7 +143,7 @@ impl FromBytes for Remove { // Read the key operand. let key = Operand::read_le(&mut reader)?; // Return the command. - Ok(Self { mapping, key }) + Ok(Self { mapping, operands: [key] }) } } @@ -153,7 +153,7 @@ impl ToBytes for Remove { // Write the mapping name. self.mapping.write_le(&mut writer)?; // Write the key operand. - self.key.write_le(&mut writer) + self.key().write_le(&mut writer) } } @@ -170,6 +170,6 @@ mod tests { assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'"); assert_eq!(remove.mapping, Identifier::from_str("account").unwrap()); assert_eq!(remove.operands().len(), 1, "The number of operands is incorrect"); - assert_eq!(remove.key, Operand::Register(Register::Locator(1)), "The first operand is incorrect"); + assert_eq!(remove.key(), &Operand::Register(Register::Locator(1)), "The first operand is incorrect"); } } diff --git a/synthesizer/program/src/logic/command/set.rs b/synthesizer/program/src/logic/command/set.rs index 7b2f4eb92f..e557faa234 100644 --- a/synthesizer/program/src/logic/command/set.rs +++ b/synthesizer/program/src/logic/command/set.rs @@ -25,10 +25,8 @@ use console::{ pub struct Set { /// The mapping name. mapping: Identifier, - /// The key to access the mapping. - key: Operand, - /// The value to be set. - value: Operand, + /// The operands. + operands: [Operand; 2], } impl Set { @@ -40,8 +38,8 @@ impl Set { /// Returns the operands in the operation. #[inline] - pub fn operands(&self) -> Vec> { - vec![self.value.clone(), self.key.clone()] + pub fn operands(&self) -> &[Operand] { + &self.operands } /// Returns the mapping name. @@ -53,13 +51,13 @@ impl Set { /// Returns the operand containing the key. #[inline] pub const fn key(&self) -> &Operand { - &self.key + &self.operands[0] } /// Returns the operand containing the value. #[inline] pub const fn value(&self) -> &Operand { - &self.value + &self.operands[1] } } @@ -71,15 +69,15 @@ impl Set { store: &impl FinalizeStoreTrait, registers: &mut impl RegistersTrait, ) -> Result> { - // Ensure the mapping exists in storage. - if !store.contains_mapping_confirmed(stack.program_id(), &self.mapping)? { - bail!("Mapping '{}/{}' does not exist in storage", stack.program_id(), self.mapping); + // Ensure the mapping exists. + if !store.contains_mapping_speculative(stack.program_id(), &self.mapping)? { + bail!("Mapping '{}/{}' does not exist", stack.program_id(), self.mapping); } // Load the key operand as a plaintext. - let key = registers.load_plaintext(stack, &self.key)?; + let key = registers.load_plaintext(stack, self.key())?; // Load the value operand as a plaintext. - let value = Value::Plaintext(registers.load_plaintext(stack, &self.value)?); + let value = Value::Plaintext(registers.load_plaintext(stack, self.value())?); // Update the value in storage, and return the finalize operation. store.update_key_value(*stack.program_id(), self.mapping, key, value) @@ -123,7 +121,7 @@ impl Parser for Set { // Parse the ";" from the string. let (string, _) = tag(";")(string)?; - Ok((string, Self { mapping, key, value })) + Ok((string, Self { mapping, operands: [key, value] })) } } @@ -158,9 +156,9 @@ impl Display for Set { // Print the command. write!(f, "{} ", Self::opcode())?; // Print the value operand. - write!(f, "{} into ", self.value)?; + write!(f, "{} into ", self.value())?; // Print the mapping and key operand. - write!(f, "{}[{}];", self.mapping, self.key) + write!(f, "{}[{}];", self.mapping, self.key()) } } @@ -174,7 +172,7 @@ impl FromBytes for Set { // Read the value operand. let value = Operand::read_le(&mut reader)?; // Return the command. - Ok(Self { mapping, key, value }) + Ok(Self { mapping, operands: [key, value] }) } } @@ -184,9 +182,9 @@ impl ToBytes for Set { // Write the mapping name. self.mapping.write_le(&mut writer)?; // Write the key operand. - self.key.write_le(&mut writer)?; + self.key().write_le(&mut writer)?; // Write the value operand. - self.value.write_le(&mut writer) + self.value().write_le(&mut writer) } } @@ -203,7 +201,7 @@ mod tests { assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'"); assert_eq!(set.mapping, Identifier::from_str("account").unwrap()); assert_eq!(set.operands().len(), 2, "The number of operands is incorrect"); - assert_eq!(set.value, Operand::Register(Register::Locator(0)), "The first operand is incorrect"); - assert_eq!(set.key, Operand::Register(Register::Locator(1)), "The second operand is incorrect"); + assert_eq!(set.value(), &Operand::Register(Register::Locator(0)), "The first operand is incorrect"); + assert_eq!(set.key(), &Operand::Register(Register::Locator(1)), "The second operand is incorrect"); } } diff --git a/synthesizer/program/src/logic/instruction/mod.rs b/synthesizer/program/src/logic/instruction/mod.rs index df0b754fc4..c28e056cbd 100644 --- a/synthesizer/program/src/logic/instruction/mod.rs +++ b/synthesizer/program/src/logic/instruction/mod.rs @@ -366,16 +366,24 @@ impl Instruction { /// The list of all instruction opcodes. pub const OPCODES: &'static [Opcode] = &instruction!(opcodes, Instruction, |None| {}); - pub fn destinations(&self) -> Vec> { - instruction!(self, |instruction| instruction.destinations()) - } - /// Returns `true` if the given name is a reserved opcode. pub fn is_reserved_opcode(name: &str) -> bool { // Check if the given name matches any opcode (in its entirety; including past the first '.' if it exists). Instruction::::OPCODES.iter().any(|opcode| **opcode == name) } + /// Returns the operands of the instruction. + #[inline] + pub fn operands(&self) -> &[Operand] { + instruction!(self, |instruction| instruction.operands()) + } + + /// Returns the destination registers of the instruction. + #[inline] + pub fn destinations(&self) -> Vec> { + instruction!(self, |instruction| instruction.destinations()) + } + /// Returns the `CallOperator` if the instruction is a `call` instruction, otherwise `None`. #[inline] pub fn call_operator(&self) -> Option<&CallOperator> { @@ -391,12 +399,6 @@ impl Instruction { instruction!(self, |InstructionMember| InstructionMember::::opcode()) } - /// Returns the operands of the instruction. - #[inline] - pub fn operands(&self) -> &[Operand] { - instruction!(self, |instruction| instruction.operands()) - } - /// Evaluates the instruction. #[inline] pub fn evaluate(&self, stack: &impl StackTrait, registers: &mut impl RegistersSigner) -> Result<()> { diff --git a/synthesizer/program/src/logic/instruction/operand/bytes.rs b/synthesizer/program/src/logic/instruction/operand/bytes.rs index a1f94ee15a..4bf2e68de0 100644 --- a/synthesizer/program/src/logic/instruction/operand/bytes.rs +++ b/synthesizer/program/src/logic/instruction/operand/bytes.rs @@ -25,6 +25,33 @@ impl FromBytes for Operand { 4 => Ok(Self::Caller), 5 => Ok(Self::BlockHeight), 6 => Ok(Self::NetworkID), + 7 => { + // Read the program ID. + let program_id = match u8::read_le(&mut reader)? { + 0 => None, + 1 => Some(ProgramID::read_le(&mut reader)?), + variant => return Err(error(format!("Invalid program ID variant '{variant}' for the checksum"))), + }; + Ok(Self::Checksum(program_id)) + } + 8 => { + // Read the program ID. + let program_id = match u8::read_le(&mut reader)? { + 0 => None, + 1 => Some(ProgramID::read_le(&mut reader)?), + variant => return Err(error(format!("Invalid program ID variant '{variant}' for the edition"))), + }; + Ok(Self::Edition(program_id)) + } + 9 => { + // Read the program ID. + let program_id = match u8::read_le(&mut reader)? { + 0 => None, + 1 => Some(ProgramID::read_le(&mut reader)?), + variant => return Err(error(format!("Invalid program ID variant '{variant}' for the owner"))), + }; + Ok(Self::ProgramOwner(program_id)) + } variant => Err(error(format!("Failed to deserialize operand variant {variant}"))), } } @@ -49,6 +76,39 @@ impl ToBytes for Operand { Self::Caller => 4u8.write_le(&mut writer), Self::BlockHeight => 5u8.write_le(&mut writer), Self::NetworkID => 6u8.write_le(&mut writer), + Self::Checksum(program_id) => { + 7u8.write_le(&mut writer)?; + // Write the program ID. + match program_id { + None => 0u8.write_le(&mut writer), + Some(program_id) => { + 1u8.write_le(&mut writer)?; + program_id.write_le(&mut writer) + } + } + } + Self::Edition(program_id) => { + 8u8.write_le(&mut writer)?; + // Write the program ID. + match program_id { + None => 0u8.write_le(&mut writer), + Some(program_id) => { + 1u8.write_le(&mut writer)?; + program_id.write_le(&mut writer) + } + } + } + Self::ProgramOwner(program_id) => { + 9u8.write_le(&mut writer)?; + // Write the program ID. + match program_id { + None => 0u8.write_le(&mut writer), + Some(program_id) => { + 1u8.write_le(&mut writer)?; + program_id.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 9a8684db75..e053605895 100644 --- a/synthesizer/program/src/logic/instruction/operand/mod.rs +++ b/synthesizer/program/src/logic/instruction/operand/mod.rs @@ -44,6 +44,18 @@ pub enum Operand { /// The operand is the network ID. /// Note: This variant is only accessible in the `finalize` scope. NetworkID, + /// The operand is the program checksum. + /// If no program ID is specified, the checksum is for the current program. + /// If a program ID is specified, the checksum is for an external program. + Checksum(Option>), + /// The operand is the program edition. + /// If no program ID is specified, the edition is for the current program. + /// If a program ID is specified, the edition is for an external program. + Edition(Option>), + /// The operand is the program owner. + /// If no program ID is specified, the owner is for the current program. + /// If a program ID is specified, the owner is for an external program. + ProgramOwner(Option>), } 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 5de2cb1911..f65986eab5 100644 --- a/synthesizer/program/src/logic/instruction/operand/parse.rs +++ b/synthesizer/program/src/logic/instruction/operand/parse.rs @@ -28,6 +28,17 @@ impl Parser for Operand { map(tag("self.caller"), |_| Self::Caller), map(tag("block.height"), |_| Self::BlockHeight), map(tag("network.id"), |_| Self::NetworkID), + // Note that `Operand::Checksum` and `Operand::Edition` must be parsed before `Operand::ProgramID`s, since an edition or checksum may be prefixed with a program ID. + map(pair(opt(terminated(ProgramID::parse, tag("/"))), tag("checksum")), |(program_id, _)| { + Self::Checksum(program_id) + }), + map(pair(opt(terminated(ProgramID::parse, tag("/"))), tag("edition")), |(program_id, _)| { + Self::Edition(program_id) + }), + // Note that `Operand::ProgramOwner` must be parsed before `Operand::ProgramID`s, since an owner may be prefixed with a program ID. + map(pair(opt(terminated(ProgramID::parse, tag("/"))), tag("program_owner")), |(program_id, _)| { + Self::ProgramOwner(program_id) + }), // 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)), @@ -80,6 +91,21 @@ impl Display for Operand { Self::BlockHeight => write!(f, "block.height"), // Prints the identifier for the network ID, i.e. network.id Self::NetworkID => write!(f, "network.id"), + // Prints the optional program ID with the checksum keyword, i.e. `checksum` or `token.aleo/checksum` + Self::Checksum(program_id) => match program_id { + Some(program_id) => write!(f, "{program_id}/checksum"), + None => write!(f, "checksum"), + }, + // Prints the optional program ID with the edition keyword, i.e. `edition` or `token.aleo/edition` + Self::Edition(program_id) => match program_id { + Some(program_id) => write!(f, "{program_id}/edition"), + None => write!(f, "edition"), + }, + // Prints the optional program ID with the program owner keyword, i.e. `program_owner` or `token.aleo/program_owner` + Self::ProgramOwner(program_id) => match program_id { + Some(program_id) => write!(f, "{program_id}/program_owner"), + None => write!(f, "program_owner"), + }, } } } @@ -120,6 +146,24 @@ mod tests { let operand = Operand::::parse("group::GEN").unwrap().1; assert_eq!(Operand::Literal(Literal::Group(Group::generator())), operand); + let operand = Operand::::parse("checksum").unwrap().1; + assert_eq!(Operand::Checksum(None), operand); + + let operand = Operand::::parse("token.aleo/checksum").unwrap().1; + assert_eq!(Operand::Checksum(Some(ProgramID::from_str("token.aleo")?)), operand); + + let operand = Operand::::parse("edition").unwrap().1; + assert_eq!(Operand::Edition(None), operand); + + let operand = Operand::::parse("token.aleo/edition").unwrap().1; + assert_eq!(Operand::Edition(Some(ProgramID::from_str("token.aleo")?)), operand); + + let operand = Operand::::parse("program_owner").unwrap().1; + assert_eq!(Operand::ProgramOwner(None), operand); + + let operand = Operand::::parse("token.aleo/program_owner").unwrap().1; + assert_eq!(Operand::ProgramOwner(Some(ProgramID::from_str("token.aleo")?)), operand); + // Sanity check a failure case. let (remainder, operand) = Operand::::parse("1field.private").unwrap(); assert_eq!(Operand::Literal(Literal::from_str("1field")?), operand); @@ -148,6 +192,24 @@ mod tests { let operand = Operand::::parse("self.caller").unwrap().1; assert_eq!(format!("{operand}"), "self.caller"); + let operand = Operand::::parse("checksum").unwrap().1; + assert_eq!(format!("{operand}"), "checksum"); + + let operand = Operand::::parse("foo.aleo/checksum").unwrap().1; + assert_eq!(format!("{operand}"), "foo.aleo/checksum"); + + let operand = Operand::::parse("edition").unwrap().1; + assert_eq!(format!("{operand}"), "edition"); + + let operand = Operand::::parse("foo.aleo/edition").unwrap().1; + assert_eq!(format!("{operand}"), "foo.aleo/edition"); + + let operand = Operand::::parse("program_owner").unwrap().1; + assert_eq!(format!("{operand}"), "program_owner"); + + let operand = Operand::::parse("foo.aleo/program_owner").unwrap().1; + assert_eq!(format!("{operand}"), "foo.aleo/program_owner"); + let operand = Operand::::parse("group::GEN").unwrap().1; assert_eq!( format!("{operand}"), diff --git a/synthesizer/program/src/parse.rs b/synthesizer/program/src/parse.rs index f9e9133806..ab9a8f565d 100644 --- a/synthesizer/program/src/parse.rs +++ b/synthesizer/program/src/parse.rs @@ -21,8 +21,9 @@ impl Parser for ProgramCore { fn parse(string: &str) -> ParserResult { // A helper to parse a program. enum P { + Constructor(ConstructorCore), M(Mapping), - I(StructType), + S(StructType), R(RecordType), C(ClosureCore), F(FunctionCore), @@ -47,10 +48,12 @@ impl Parser for ProgramCore { // Parse the whitespace and comments from the string. let (string, _) = Sanitizer::parse(string)?; - if string.starts_with(Mapping::::type_name()) { + if string.starts_with(ConstructorCore::::type_name()) { + map(ConstructorCore::parse, |constructor| P::::Constructor(constructor))(string) + } else if string.starts_with(Mapping::::type_name()) { map(Mapping::parse, |mapping| P::::M(mapping))(string) } else if string.starts_with(StructType::::type_name()) { - map(StructType::parse, |struct_| P::::I(struct_))(string) + map(StructType::parse, |struct_| P::::S(struct_))(string) } else if string.starts_with(RecordType::::type_name()) { map(RecordType::parse, |record| P::::R(record))(string) } else if string.starts_with(ClosureCore::::type_name()) { @@ -78,8 +81,9 @@ impl Parser for ProgramCore { // Construct the program with the parsed components. for component in components { let result = match component { + P::Constructor(constructor) => program.add_constructor(constructor), P::M(mapping) => program.add_mapping(mapping), - P::I(struct_) => program.add_struct(struct_), + P::S(struct_) => program.add_struct(struct_), P::R(record) => program.add_record(record), P::C(closure) => program.add_closure(closure), P::F(function) => program.add_function(function), @@ -151,32 +155,43 @@ impl Display for ProgramCore { // Print the program name. write!(f, "{} {};\n\n", Self::type_name(), self.id)?; - let mut identifier_iter = self.components.iter().peekable(); - while let Some((identifier, definition)) = identifier_iter.next() { - match definition { - ProgramDefinition::Mapping => match self.mappings.get(identifier) { - Some(mapping) => writeln!(f, "{mapping}")?, - None => return Err(fmt::Error), - }, - ProgramDefinition::Struct => match self.structs.get(identifier) { - Some(struct_) => writeln!(f, "{struct_}")?, - None => return Err(fmt::Error), - }, - ProgramDefinition::Record => match self.records.get(identifier) { - Some(record) => writeln!(f, "{record}")?, - None => return Err(fmt::Error), - }, - ProgramDefinition::Closure => match self.closures.get(identifier) { - Some(closure) => writeln!(f, "{closure}")?, - None => return Err(fmt::Error), - }, - ProgramDefinition::Function => match self.functions.get(identifier) { - Some(function) => writeln!(f, "{function}")?, - None => return Err(fmt::Error), + // Write the components. + let mut components_iter = self.components.iter().peekable(); + while let Some((label, definition)) = components_iter.next() { + match label { + ProgramLabel::Constructor => { + // Write the constructor, if it exists. + if let Some(constructor) = &self.constructor { + writeln!(f, "{constructor}")?; + } + } + ProgramLabel::Identifier(identifier) => match definition { + ProgramDefinition::Constructor => return Err(fmt::Error), + ProgramDefinition::Mapping => match self.mappings.get(identifier) { + Some(mapping) => writeln!(f, "{mapping}")?, + None => return Err(fmt::Error), + }, + ProgramDefinition::Struct => match self.structs.get(identifier) { + Some(struct_) => writeln!(f, "{struct_}")?, + None => return Err(fmt::Error), + }, + ProgramDefinition::Record => match self.records.get(identifier) { + Some(record) => writeln!(f, "{record}")?, + None => return Err(fmt::Error), + }, + ProgramDefinition::Closure => match self.closures.get(identifier) { + Some(closure) => writeln!(f, "{closure}")?, + None => return Err(fmt::Error), + }, + ProgramDefinition::Function => match self.functions.get(identifier) { + Some(function) => writeln!(f, "{function}")?, + None => return Err(fmt::Error), + }, }, } + // Omit the last newline. - if identifier_iter.peek().is_some() { + if components_iter.peek().is_some() { writeln!(f)?; } } diff --git a/synthesizer/process/src/stack/registers/call.rs b/synthesizer/program/src/to_checksum.rs similarity index 56% rename from synthesizer/process/src/stack/registers/call.rs rename to synthesizer/program/src/to_checksum.rs index eaee935084..4ee79963fc 100644 --- a/synthesizer/process/src/stack/registers/call.rs +++ b/synthesizer/program/src/to_checksum.rs @@ -15,16 +15,17 @@ use super::*; -impl> RegistersCall for Registers { - /// Returns the current call stack. - #[inline] - fn call_stack(&self) -> CallStack { - self.call_stack.clone() - } +impl ProgramCore { + /// Returns the checksum of the program. + /// + /// The checksum is a 32-byte hash of the program's source code in string format. + /// This ensures a strict definition of program equivalence, useful for program upgradability. + pub fn to_checksum(&self) -> [U8; 32] { + let mut keccak = TinySha3::v256(); + keccak.update(self.to_string().as_bytes()); - /// Returns a reference to the current call stack. - #[inline] - fn call_stack_ref(&self) -> &CallStack { - &self.call_stack + let mut hash = [0u8; 32]; + keccak.finalize(&mut hash); + hash.map(U8::new) } } diff --git a/synthesizer/program/src/traits/finalize_store.rs b/synthesizer/program/src/traits/finalize_store.rs index 38c61b7d3f..36504e9e49 100644 --- a/synthesizer/program/src/traits/finalize_store.rs +++ b/synthesizer/program/src/traits/finalize_store.rs @@ -21,9 +21,16 @@ use console::{ }; pub trait FinalizeStoreTrait { - /// Returns `true` if the given `program ID` and `mapping name` exist. + /// Returns `true` if the given `program ID` and `mapping name` is confirmed to exist. fn contains_mapping_confirmed(&self, program_id: &ProgramID, mapping_name: &Identifier) -> Result; + /// Returns `true` if the given `program ID` and `mapping name` exist. + /// This method was added to support execution of constructors during deployment. + /// Prior to supporting program upgrades, `contains_mapping_confirmed` was used to check that a mapping exists before executing a command like `set`, `get`, `remove`, etc. + /// However, during deployment, the mapping only speculatively exists, so `contains_mapping_speculative` should be used instead. + /// This usage is safe because the mappings used in a program are statically verified to exist in `FinalizeTypes::from_*` before the deployment or upgrade's constructor is executed. + fn contains_mapping_speculative(&self, program_id: &ProgramID, mapping_name: &Identifier) -> Result; + /// Returns `true` if the given `program ID`, `mapping name`, and `key` exist. fn contains_key_speculative( &self, diff --git a/synthesizer/program/src/traits/stack_and_registers.rs b/synthesizer/program/src/traits/stack_and_registers.rs index 1c4d451a1d..8915267945 100644 --- a/synthesizer/program/src/traits/stack_and_registers.rs +++ b/synthesizer/program/src/traits/stack_and_registers.rs @@ -34,7 +34,7 @@ use console::{ Value, ValueType, }, - types::{Address, Field, U16}, + types::{Address, Field, U8, U16}, }; use rand::{CryptoRng, Rng}; use snarkvm_synthesizer_snark::{ProvingKey, VerifyingKey}; @@ -94,9 +94,22 @@ pub trait StackTrait { /// Returns the program address. fn program_address(&self) -> &Address; + /// Returns the program checksum. + fn program_checksum(&self) -> &[U8; 32]; + + /// Returns the program checksum as a field element. + fn program_checksum_as_field(&self) -> Result>; + /// Returns the program edition. fn program_edition(&self) -> U16; + /// Returns the program owner. + /// The program owner should only be set for programs that are deployed after `ConsensusVersion::V9` is active. + fn program_owner(&self) -> &Option>; + + /// Sets the program owner. + fn set_program_owner(&mut self, program_owner: Option>); + /// Returns the external stack for the given program ID. fn get_external_stack(&self, program_id: &ProgramID) -> Result>; diff --git a/synthesizer/src/vm/deploy.rs b/synthesizer/src/vm/deploy.rs index ccb5400506..ded304c3ee 100644 --- a/synthesizer/src/vm/deploy.rs +++ b/synthesizer/src/vm/deploy.rs @@ -32,16 +32,31 @@ impl> VM { rng: &mut R, ) -> Result> { // Compute the deployment. - let deployment = self.deploy_raw(program, rng)?; + let mut deployment = self.deploy_raw(program, rng)?; // Ensure the transaction is not empty. ensure!(!deployment.program().functions().is_empty(), "Attempted to create an empty transaction deployment"); + // Get a default query if one is not provided. + let query = match query { + Some(q) => q, + None => &Query::VM(self.block_store().clone()), + }; + // If the `CONSENSUS_VERSION` is less than `V9`, unset the program checksum and the owner. + // Otherwise, swap the default owner with the address of the private key. + let consensus_version = N::CONSENSUS_VERSION(query.current_block_height()?)?; + if consensus_version < ConsensusVersion::V9 { + deployment.set_program_checksum_raw(None); + deployment.set_program_owner_raw(None) + } else { + deployment.set_program_checksum_raw(Some(deployment.program().to_checksum())); + deployment.set_program_owner_raw(Some(Address::try_from(private_key)?)); + } // Compute the deployment ID. let deployment_id = deployment.to_deployment_id()?; // Construct the owner. let owner = ProgramOwner::new(private_key, deployment_id, rng)?; // Compute the minimum deployment cost. - let (minimum_deployment_cost, _) = deployment_cost(&deployment)?; + let (minimum_deployment_cost, _) = deployment_cost(&self.process().read(), &deployment)?; // Authorize the fee. let fee_authorization = match fee_record { Some(record) => self.authorize_fee_private( @@ -61,7 +76,7 @@ impl> VM { )?, }; // Compute the fee. - let fee = self.execute_fee_authorization(fee_authorization, query, rng)?; + let fee = self.execute_fee_authorization(fee_authorization, Some(query), rng)?; // Return the deploy transaction. Transaction::from_deployment(owner, deployment, fee) diff --git a/synthesizer/src/vm/execute.rs b/synthesizer/src/vm/execute.rs index 8f3f970cd5..b7e4b6a1b7 100644 --- a/synthesizer/src/vm/execute.rs +++ b/synthesizer/src/vm/execute.rs @@ -957,6 +957,8 @@ finalize test: let function_name = transition.function_name(); // Get the stack. let stack = vm.process().read().get_stack(program_id).unwrap().clone(); + // Get the finalize types. + let finalize_types = stack.get_finalize_types(function_name).unwrap(); // Get the finalize block of the transition and sum the cost of each command. let cost = match stack.get_function(function_name).unwrap().finalize_logic() { None => 0, @@ -965,7 +967,7 @@ finalize test: finalize_logic .commands() .iter() - .map(|command| cost_per_command(&stack, finalize_logic, command, ConsensusFeeVersion::V2)) + .map(|command| cost_per_command(&stack, &finalize_types, command, ConsensusFeeVersion::V2)) .try_fold(0u64, |acc, res| { res.and_then(|x| acc.checked_add(x).ok_or(anyhow!("Finalize cost overflowed"))) }) @@ -1093,6 +1095,8 @@ finalize test: let function_name = transition.function_name(); // Get the stack. let stack = vm.process().read().get_stack(program_id).unwrap().clone(); + // Get the finalize types. + let finalize_types = stack.get_finalize_types(function_name).unwrap(); // Get the finalize block of the transition and sum the cost of each command. let cost = match stack.get_function(function_name).unwrap().finalize_logic() { None => 0, @@ -1101,7 +1105,7 @@ finalize test: finalize_logic .commands() .iter() - .map(|command| cost_per_command(&stack, finalize_logic, command, ConsensusFeeVersion::V2)) + .map(|command| cost_per_command(&stack, &finalize_types, command, ConsensusFeeVersion::V2)) .try_fold(0u64, |acc, res| { res.and_then(|x| acc.checked_add(x).ok_or(anyhow!("Finalize cost overflowed"))) }) diff --git a/synthesizer/src/vm/finalize.rs b/synthesizer/src/vm/finalize.rs index 94292beded..1a3fcb9520 100644 --- a/synthesizer/src/vm/finalize.rs +++ b/synthesizer/src/vm/finalize.rs @@ -291,8 +291,6 @@ impl> VM { let mut confirmed = Vec::with_capacity(num_transactions); // Initialize a list of the aborted transactions. let mut aborted = Vec::new(); - // Initialize a list of the successful deployments. - let mut deployments = IndexSet::new(); // Initialize a counter for the confirmed transaction index. let mut counter = 0u32; // Initialize a list of created transition IDs. @@ -305,6 +303,8 @@ impl> VM { let mut tpks: IndexSet> = IndexSet::new(); // Initialize the list of deployment payers. let mut deployment_payers: IndexSet> = IndexSet::new(); + // Initialize a list of the successful deployments. + let mut deployments = IndexSet::new(); // Finalize the transactions. 'outer: for transaction in transactions { @@ -325,6 +325,7 @@ impl> VM { &output_ids, &tpks, &deployment_payers, + &deployments, ) { // Store the aborted transaction. aborted.push((transaction.clone(), reason)); @@ -376,8 +377,6 @@ impl> VM { Ok((stack, finalize)) => { // Add the stack to the process with the option to be reverted. process.stage_stack(stack); - // Add the program id to the list of deployments. - deployments.insert(*deployment.program_id()); ConfirmedTransaction::accepted_deploy(counter, transaction.clone(), finalize) .map_err(|e| e.to_string()) } @@ -470,9 +469,10 @@ impl> VM { output_ids.extend(confirmed_transaction.transaction().output_ids()); // Add the transition public keys to the set of produced transition public keys. tpks.extend(confirmed_transaction.transaction().transition_public_keys()); - // Add any public deployment payer to the set of deployment payers. - if let Transaction::Deploy(_, _, _, _, fee) = confirmed_transaction.transaction() { + // Add the program owner to the set of deployment payers and the program ID to the set of deployments. + if let Transaction::Deploy(_, _, _, deployment, fee) = confirmed_transaction.transaction() { fee.payer().map(|payer| deployment_payers.insert(payer)); + deployments.insert(*deployment.program_id()); } // Store the confirmed transaction. confirmed.push(confirmed_transaction); @@ -806,6 +806,11 @@ impl> VM { /// - The transaction is producing a duplicate output /// - The transaction is producing a duplicate transition public key /// - The transaction is another deployment in the block from the same public fee payer. + /// - The transaction contains a transition that has been deployed or upgraded in this block. + /// + /// - Note: If a transaction is a deployment for a program following its deployment or redeployment in this block, + /// it is not aborted. Instead, it will be rejected and its fee will be consumed. + #[allow(clippy::too_many_arguments)] fn should_abort_transaction( &self, transaction: &Transaction, @@ -814,15 +819,27 @@ impl> VM { output_ids: &IndexSet>, tpks: &IndexSet>, deployment_payers: &IndexSet>, + deployments: &IndexSet>, ) -> Option { - // Ensure that the transaction is not producing a duplicate transition. - for transition_id in transaction.transition_ids() { + // Ensure that: + // - the transaction is not producing a duplicate transition. + // - the programs in the component transitions haven't been deployed or upgraded in this block. + for transition in transaction.transitions() { + // Get the transition ID. + let transition_id = transition.id(); // If the transition ID is already produced in this block or previous blocks, abort the transaction. if transition_ids.contains(transition_id) || self.transition_store().contains_transition_id(transition_id).unwrap_or(true) { return Some(format!("Duplicate transition {transition_id}")); } + // If the transition's program is being deployed or redeployed in this block, abort the transaction. + if deployments.contains(transition.program_id()) { + return Some(format!( + "Program {} is being deployed or redeployed in this block", + transition.program_id() + )); + } } // Ensure that the transaction is not double-spending an input. @@ -890,6 +907,8 @@ impl> VM { let mut tpks: IndexSet> = Default::default(); // Initialize the list of deployment payers. let mut deployment_payers: IndexSet> = Default::default(); + // Initialize a list of the successful deployments. + let mut deployments = IndexSet::new(); // Abort the transactions that are have duplicates or are invalid. This will prevent the VM from performing // verification on transactions that would have been aborted in `VM::atomic_speculate`. @@ -908,6 +927,7 @@ impl> VM { &output_ids, &tpks, &deployment_payers, + &deployments, ) { // Store the aborted transaction. Some(reason) => aborted_transactions.push((*transaction, reason.to_string())), @@ -921,9 +941,10 @@ impl> VM { output_ids.extend(transaction.output_ids()); // Add the transition public keys to the set of produced transition public keys. tpks.extend(transaction.transition_public_keys()); - // Add any public deployment payer to the set of deployment payers. - if let Transaction::Deploy(_, _, _, _, fee) = transaction { + // Add the program owner to the set of deployment payers and the program ID to the set of deployments. + if let Transaction::Deploy(_, _, _, deployment, fee) = transaction { fee.payer().map(|payer| deployment_payers.insert(payer)); + deployments.insert(*deployment.program_id()); } // Add the transaction to the list of transactions to verify. diff --git a/synthesizer/src/vm/mod.rs b/synthesizer/src/vm/mod.rs index 65368daeb5..b897bc114e 100644 --- a/synthesizer/src/vm/mod.rs +++ b/synthesizer/src/vm/mod.rs @@ -30,7 +30,7 @@ use console::{ account::{Address, PrivateKey}, network::prelude::*, program::{Argument, Identifier, Literal, Locator, Plaintext, ProgramID, ProgramOwner, Record, Response, Value}, - types::{Field, Group, U64}, + types::{Field, Group, U16, U64}, }; use snarkvm_algorithms::snark::varuna::VarunaVersion; use snarkvm_ledger_block::{ @@ -95,6 +95,11 @@ use std::{collections::HashSet, num::NonZeroUsize, sync::Arc}; #[cfg(not(feature = "serial"))] use rayon::prelude::*; +// The key for the partially-verified transactions cache. +// The key is a tuple of the transaction ID and a list of program checksums for the transitions in the transaction. +// Note: If a program is upgraded and its contents are changed, then the program checksums will change, invalidating the previously cached result. +type TransactionCacheKey = (::TransactionID, Vec>); + #[derive(Clone)] pub struct VM> { /// The process. @@ -104,7 +109,7 @@ pub struct VM> { /// The VM store. store: ConsensusStore, /// A cache containing the list of recent partially-verified transactions. - partially_verified_transactions: Arc>>, + partially_verified_transactions: Arc, N::TransmissionChecksum>>>, /// The restrictions list. restrictions: Restrictions, /// The lock to guarantee atomicity over calls to speculate and finalize. @@ -233,7 +238,9 @@ impl> VM { /// Returns the partially-verified transactions. #[inline] - pub fn partially_verified_transactions(&self) -> Arc>> { + pub fn partially_verified_transactions( + &self, + ) -> Arc, N::TransmissionChecksum>>> { self.partially_verified_transactions.clone() } @@ -536,7 +543,7 @@ pub(crate) mod test_helpers { use std::sync::OnceLock; pub(crate) type CurrentNetwork = MainnetV0; - type CurrentAleo = AleoV0; + pub(crate) type CurrentAleo = AleoV0; #[cfg(not(feature = "rocks"))] pub(crate) type LedgerType = snarkvm_ledger_store::helpers::memory::ConsensusMemory; @@ -556,18 +563,29 @@ pub(crate) mod test_helpers { #[cfg(feature = "test")] pub(crate) fn sample_vm_at_height(height: u32, rng: &mut TestRng) -> VM { // Initialize the VM with a genesis block. - let vm = sample_vm_with_genesis_block(rng); + let mut vm = sample_vm_with_genesis_block(rng); // Get the genesis private key. let genesis_private_key = sample_genesis_private_key(rng); // Advance the VM to the given height. - for _ in 0..height { - let block = sample_next_block(&vm, &genesis_private_key, &[], rng).unwrap(); - vm.add_next_block(&block).unwrap(); - } + advance_vm_to_height(&mut vm, genesis_private_key, height, rng); // Return the VM. vm } + #[cfg(feature = "test")] + pub(crate) fn advance_vm_to_height( + vm: &mut VM, + genesis_private_key: PrivateKey, + height: u32, + rng: &mut TestRng, + ) { + // Advance the VM to the given height. + for _ in vm.block_store().current_block_height()..height { + let block = sample_next_block(vm, &genesis_private_key, &[], rng).unwrap(); + vm.add_next_block(&block).unwrap(); + } + } + pub(crate) fn sample_genesis_private_key(rng: &mut TestRng) -> PrivateKey { static INSTANCE: OnceLock> = OnceLock::new(); *INSTANCE.get_or_init(|| { @@ -861,7 +879,7 @@ function compute: // Construct the new block header. let time_since_last_block = MainnetV0::BLOCK_TIME as i64; let (ratifications, transactions, aborted_transaction_ids, ratified_finalize_operations) = vm.speculate( - sample_finalize_state(1), + sample_finalize_state(vm.block_store().current_block_height() + 1), time_since_last_block, None, vec![], @@ -1554,8 +1572,14 @@ function do: let fee = vm.execute_fee_authorization(fee_authorization, None, rng).unwrap(); // Create a new deployment transaction with the overreported verifying keys. - let adjusted_deployment = - Deployment::new(deployment.edition(), deployment.program().clone(), vks_with_overreport).unwrap(); + let adjusted_deployment = Deployment::new( + deployment.edition(), + deployment.program().clone(), + vks_with_overreport, + deployment.program_checksum(), + deployment.program_owner(), + ) + .unwrap(); let adjusted_transaction = Transaction::from_deployment(program_owner, adjusted_deployment, fee).unwrap(); // Verify the deployment transaction. It should error when certificate checking for constraint count mismatch. @@ -1609,8 +1633,14 @@ function do: } // Create a new deployment transaction with the underreported verifying keys. - let adjusted_deployment = - Deployment::new(deployment.edition(), deployment.program().clone(), vks_with_underreport).unwrap(); + let adjusted_deployment = Deployment::new( + deployment.edition(), + deployment.program().clone(), + vks_with_underreport, + deployment.program_checksum(), + deployment.program_owner(), + ) + .unwrap(); let deployment_id = adjusted_deployment.to_deployment_id().unwrap(); let adjusted_transaction = Transaction::Deploy(txid, deployment_id, program_owner, Box::new(adjusted_deployment), fee); @@ -1684,8 +1714,14 @@ function do: } // Create a new deployment transaction with the underreported verifying keys. - let adjusted_deployment = - Deployment::new(deployment.edition(), deployment.program().clone(), vks_with_underreport).unwrap(); + let adjusted_deployment = Deployment::new( + deployment.edition(), + deployment.program().clone(), + vks_with_underreport, + deployment.program_checksum(), + deployment.program_owner(), + ) + .unwrap(); let deployment_id = adjusted_deployment.to_deployment_id().unwrap(); let adjusted_transaction = Transaction::Deploy(txid, deployment_id, program_owner, Box::new(adjusted_deployment), fee); @@ -3185,6 +3221,7 @@ function check: vm.add_next_block(&genesis).unwrap(); // Fund two accounts to pay for the deployment. + // This has to be done because only one deployment can be made per fee-paying address per block. let private_key_1 = PrivateKey::new(rng).unwrap(); let private_key_2 = PrivateKey::new(rng).unwrap(); let address_1 = Address::try_from(&private_key_1).unwrap(); @@ -3215,6 +3252,8 @@ function check: let block = sample_next_block(&vm, &caller_private_key, &[tx_1, tx_2], rng).unwrap(); assert_eq!(block.transactions().num_accepted(), 2); + assert_eq!(block.transactions().num_rejected(), 0); + assert_eq!(block.aborted_transaction_ids().len(), 0); vm.add_next_block(&block).unwrap(); // Deploy two programs that depend on each other. @@ -3265,6 +3304,8 @@ function adder: let block = sample_next_block(&vm, &caller_private_key, &[deployment_1, deployment_2], rng).unwrap(); assert_eq!(block.transactions().num_accepted(), 1); + assert_eq!(block.transactions().num_rejected(), 0); + assert_eq!(block.aborted_transaction_ids().len(), 1); vm.add_next_block(&block).unwrap(); // Check that only `child_program.aleo` is in the VM. diff --git a/synthesizer/src/vm/tests/mod.rs b/synthesizer/src/vm/tests/mod.rs index f7b3bdaf17..4a3a8d93df 100644 --- a/synthesizer/src/vm/tests/mod.rs +++ b/synthesizer/src/vm/tests/mod.rs @@ -16,5 +16,8 @@ #[cfg(feature = "test")] mod test_v8; +#[cfg(feature = "test")] +mod test_v9; + #[cfg(feature = "test")] use super::*; diff --git a/synthesizer/src/vm/tests/test_v8.rs b/synthesizer/src/vm/tests/test_v8.rs index c0dd44a0c1..3e464b93e1 100644 --- a/synthesizer/src/vm/tests/test_v8.rs +++ b/synthesizer/src/vm/tests/test_v8.rs @@ -21,7 +21,7 @@ use console::{account::ViewKey, network::ConsensusVersion}; use snarkvm_ledger_store::ConsensusStore; use snarkvm_synthesizer_program::{Program, StackTrait as _}; -use crate::vm::test_helpers::sample_vm_at_height; +use crate::vm::test_helpers::{advance_vm_to_height, sample_vm_at_height}; use aleo_std::StorageMode; use console::program::ProgramOwner; use snarkvm_ledger_block::{Deployment, Transaction}; @@ -29,9 +29,9 @@ use snarkvm_ledger_block::{Deployment, Transaction}; // This test checks that: // - an existing program cannot be redeployed before `ConsensusVersion::V8` // - an existing program cannot be redeployed with different code after `ConsensusVersion::V8` -// - an existing program can be redeployed with the same code after `ConsensusVersion::V8` -// - an existing program can only be redeployed once after `ConsensusVersion::V8` -// - a program with a mapping can be redeployed after `ConsensusVersion::V8` +// - an existing program can be redeployed with the same code after `ConsensusVersion::V8` (even after `V9`) +// - an existing program can only be redeployed once after `ConsensusVersion::V8` (even after `V9`) +// - a program with a mapping can be redeployed after `ConsensusVersion::V8` (even after `V9`) // - after `ConsensusVersion::V8`, existing programs cannot be executed until they are redeployed. // - the VM can be loaded from a store at the very end. #[test] @@ -45,7 +45,7 @@ fn test_redeployment() -> Result<()> { let store = ConsensusStore::::open(StorageMode::new_test(None)).unwrap(); // Initialize the VM. - let vm = VM::::from(store.clone())?; + let mut vm = VM::::from(store.clone())?; let genesis = sample_genesis_block(rng); vm.add_next_block(&genesis)?; @@ -53,11 +53,12 @@ fn test_redeployment() -> Result<()> { let genesis_private_key = sample_genesis_private_key(rng); // Advance the VM to 3 blocks before `ConsensusVersion::V8`. - let desired_height = CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V8)? - 3; - for _ in 0..desired_height { - let block = sample_next_block(&vm, &genesis_private_key, &[], rng).unwrap(); - vm.add_next_block(&block).unwrap(); - } + advance_vm_to_height( + &mut vm, + genesis_private_key, + CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V8)? - 3, + rng, + ); // Initialize the programs let program = Program::from_str( @@ -162,7 +163,12 @@ function dummy2: vm.add_next_block(&block)?; // Attempt to redeploy the program again after `ConsensusVersion::V8`. - assert!(vm.deploy(&caller_private_key, &program, None, 0, None, rng).is_err()); + let transaction = vm.deploy(&caller_private_key, &program, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + assert_eq!(block.transactions().num_accepted(), 0); + assert_eq!(block.transactions().num_rejected(), 0); + assert_eq!(block.aborted_transaction_ids().len(), 1); + vm.add_next_block(&block)?; // Drop the VM. drop(vm); @@ -172,7 +178,7 @@ function dummy2: // Check that the latest block. let latest_block = vm.store.block_store().current_block_height(); - assert_eq!(latest_block, CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V8)? + 3); + assert_eq!(latest_block, CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V8)? + 4); // Check that the program can still be executed. let execute = vm.execute( @@ -337,7 +343,8 @@ finalize run: // This test verifies that: // - a program cannot be redeployed in the same block as its deployment // - an edition 0 program cannot be executed if it is deployed after `ConsensusVersion::V8` -// - a program can be redeployed using the exact same deployment, different fee, and in a different block, after `ConsensusVersion::V8` +// - a program can be redeployed using the exact same deployment, different fee, and in a different block, after `ConsensusVersion::V8` (even after `ConsensusVersion::V9`) +// Note: It is important that this invariant holds, otherwise block rollbacks in the DB can be inconsistent. #[test] fn test_deploy_and_redeploy() -> Result<()> { let rng = &mut TestRng::default(); @@ -350,7 +357,7 @@ fn test_deploy_and_redeploy() -> Result<()> { let address = Address::try_from(&other_private_key)?; // Initialize the VM. - let vm = sample_vm_at_height(CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V8)?, rng); + let vm = sample_vm_at_height(CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V8)? - 1, rng); // Send some credits to the second caller. let transfer = vm.execute( @@ -385,6 +392,8 @@ function dummy: deployment_0.edition() + 1, deployment_0.program().clone(), deployment_0.verifying_keys().clone(), + deployment_0.program_checksum(), + deployment_0.program_owner(), )?; let fee_authorization = vm.authorize_fee_public( &other_private_key, @@ -424,10 +433,18 @@ function dummy: vm.add_next_block(&block)?; // Redeploy the program with the other private key, using the original deployment. - let deployment = Deployment::new(1, program.clone(), deployment_0.verifying_keys().clone())?; + let deployment = Deployment::new( + 1, + program.clone(), + deployment_0.verifying_keys().clone(), + Some(deployment_0.program().to_checksum()), + Some(address), + )?; + // Note: This needs to be recalculated since the new deployment contains a checksum and owner. + let (base_fee_amount, _) = deployment_cost(&vm.process.read(), &deployment)?; let fee_authorization = vm.authorize_fee_public( &other_private_key, - *transaction_0.base_fee_amount()?, + base_fee_amount, *transaction_0.priority_fee_amount()?, deployment.to_deployment_id()?, rng, @@ -458,7 +475,11 @@ function dummy: vm.add_next_block(&block)?; // Verify that the program cannot be redeployed further. - assert!(vm.deploy(&other_private_key, &program, None, 0, None, rng).is_err()); + let transaction = vm.deploy(&other_private_key, &program, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + assert_eq!(block.transactions().num_accepted(), 0); + assert_eq!(block.transactions().num_rejected(), 0); + assert_eq!(block.aborted_transaction_ids().len(), 1); Ok(()) } diff --git a/synthesizer/src/vm/tests/test_v9.rs b/synthesizer/src/vm/tests/test_v9.rs new file mode 100644 index 0000000000..a295af44f0 --- /dev/null +++ b/synthesizer/src/vm/tests/test_v9.rs @@ -0,0 +1,3099 @@ +// 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 console::{account::ViewKey, program::Value}; +use snarkvm_synthesizer_program::{Program, StackTrait}; + +use crate::vm::test_helpers::{advance_vm_to_height, sample_vm_at_height}; +use aleo_std::StorageMode; +use console::{ + account::{Address, Field, PrivateKey}, + network::ConsensusVersion, + program::{Identifier, Literal, Plaintext, ProgramID, ProgramOwner}, +}; +use snarkvm_ledger_block::Transaction; +use snarkvm_ledger_store::ConsensusStore; +use snarkvm_synthesizer_process::Process; +use snarkvm_utilities::TestRng; +use std::panic::AssertUnwindSafe; + +// This test checks that: +// - programs without constructors can be deployed before V9 +// - programs with constructors cannot be deployed before V9 +// - programs without constructor cannot be deployed after V9 +// - program with constructors can be deployed after V9 +#[test] +fn test_constructor_requires_v9() -> Result<()> { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + + // Initialize the VM. + let vm = sample_vm_at_height(CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V9)? - 2, rng); + + // Initialize the program. + let program = Program::from_str( + r" +program constructor_test_0.aleo; + +constructor: + assert.eq true true; + +function dummy: + ", + )?; + + // Attempt to deploy the program. + let deployment = vm.deploy(&caller_private_key, &program, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[deployment], rng)?; + assert_eq!(block.transactions().num_accepted(), 0); + assert_eq!(block.transactions().num_rejected(), 0); + assert_eq!(block.aborted_transaction_ids().len(), 1); + vm.add_next_block(&block)?; + + // Initialize the program. + let program = Program::from_str( + r" +program no_constructor_test_0.aleo; + +function dummy: + ", + )?; + + // Attempt to deploy the program. + let deployment = vm.deploy(&caller_private_key, &program, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[deployment], rng)?; + 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)?; + + // Verify that the VM is at the V9 height. + assert_eq!(vm.block_store().current_block_height(), CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V9)?); + + // Initialize the program. + let program = Program::from_str( + r" +program constructor_test_1.aleo; + +constructor: + assert.eq true true; + +function dummy: + ", + )?; + + // Attempt to deploy the program. + let deployment = vm.deploy(&caller_private_key, &program, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[deployment], rng)?; + 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)?; + + // Initialize the program. + let program = Program::from_str( + r" +program no_constructor_test_1.aleo; + +function dummy: + ", + )?; + + // Attempt to deploy the program. + let deployment = vm.deploy(&caller_private_key, &program, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[deployment], rng)?; + assert_eq!(block.transactions().num_accepted(), 0); + assert_eq!(block.transactions().num_rejected(), 0); + assert_eq!(block.aborted_transaction_ids().len(), 1); + vm.add_next_block(&block)?; + + Ok(()) +} + +// This test checks that: +// - the logic of a simple transition without records can be upgraded. +// - once a program is upgraded, the old executions are no longer valid. +// - a constructor with an "allow any" policy can be upgraded by anyone. +// - a program can be upgraded to a new edition with the exact same logic. +#[test] +fn test_simple_upgrade() -> Result<()> { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + + // Initialize the VM. + let vm = sample_vm_at_height(CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V9)?, rng); + + // Initialize the program. + let program = Program::from_str( + r" +program adder.aleo; + +function binary_add: + input r0 as u8.public; + input r1 as u8.public; + add r0 r1 into r2; + output r2 as u8.public; + +constructor: + assert.eq true true; + ", + )?; + + // Deploy the program. + let transaction = vm.deploy(&caller_private_key, &program, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + 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)?; + + // Check that the program is deployed. + let stack = vm.process().read().get_stack("adder.aleo")?; + assert_eq!(stack.program_id(), &ProgramID::from_str("adder.aleo")?); + assert_eq!(*stack.program_edition(), 0); + + // Execute the program. + let original_execution = vm.execute( + &caller_private_key, + ("adder.aleo", "binary_add"), + vec![Value::from_str("1u8")?, Value::from_str("1u8")?].into_iter(), + None, + 0, + None, + rng, + )?; + assert!(vm.check_transaction(&original_execution, None, rng).is_ok()); + + // Check that the output is correct. + let output = match original_execution.transitions().next().unwrap().outputs().last().unwrap() { + Output::Public(_, Some(Plaintext::Literal(Literal::U8(value), _))) => **value, + output => bail!(format!("Unexpected output: {output}")), + }; + assert_eq!(output, 2u8); + + // Initialize a new caller. + let user_private_key = PrivateKey::new(rng).unwrap(); + let user_address = Address::try_from(&user_private_key)?; + + // Fund the user with a `transfer_public` transaction. + let transaction = vm.execute( + &caller_private_key, + ("credits.aleo", "transfer_public"), + vec![Value::from_str(&format!("{user_address}"))?, Value::from_str("1_000_000_000_000u64")?].into_iter(), + None, + 0, + None, + rng, + )?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + 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)?; + + // Upgrade the program. + let upgraded_program = Program::from_str( + r" +program adder.aleo; + +function binary_add: + input r0 as u8.public; + input r1 as u8.public; + add.w r0 r1 into r2; + output r2 as u8.public; + +constructor: + assert.eq true true; + ", + )?; + + // Deploy the upgraded program. + let transaction = vm.deploy(&user_private_key, &upgraded_program, None, 0, None, rng)?; + assert_eq!(transaction.deployment().unwrap().edition(), 1); + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + 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)?; + + // Check that the program is upgraded. + let stack = vm.process().read().get_stack("adder.aleo")?; + assert_eq!(stack.program_id(), &ProgramID::from_str("adder.aleo")?); + assert_eq!(*stack.program_edition(), 1); + + // Check that the old execution is no longer valid. + assert!(vm.check_transaction(&original_execution, None, rng).is_err()); + + // Upgrade the program with the same locig. + let transaction = vm.deploy(&user_private_key, &upgraded_program, None, 0, None, rng)?; + assert_eq!(transaction.deployment().unwrap().edition(), 2); + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + 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)?; + + // Execute the upgraded program. + let new_execution = vm.execute( + &user_private_key, + ("adder.aleo", "binary_add"), + vec![Value::from_str("1u8")?, Value::from_str("1u8")?].into_iter(), + None, + 0, + None, + rng, + )?; + assert!(vm.check_transaction(&new_execution, None, rng).is_ok()); + + // Check that the output is correct. + let output = match new_execution.transitions().next().unwrap().outputs().last().unwrap() { + Output::Public(_, Some(Plaintext::Literal(Literal::U8(value), _))) => **value, + output => bail!(format!("Unexpected output: {output}")), + }; + assert_eq!(output, 2u8); + + Ok(()) +} + +// This test checks that: +// - the first instance of a program must be the zero-th edition. +// - subsequent upgrades to the program must be sequential. +#[test] +fn test_editions_are_sequential() -> Result<()> { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + + // Initialize two VMs. + let off_chain_vm = sample_vm_at_height(CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V9)?, rng); + let on_chain_vm = sample_vm_at_height(CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V9)?, rng); + + // Define the three versions of the program. + let program_v0 = Program::from_str( + r" +program basic.aleo; +function foo: +constructor: + assert.eq true true; + ", + )?; + let program_v1 = Program::from_str( + r" +program basic.aleo; +function foo: +function bar: +constructor: + assert.eq true true; + ", + )?; + let program_v2_as_v1 = Program::from_str( + r" +program basic.aleo; +function foo: +function bar: +function baz: +constructor: + assert.eq true true; + ", + )?; + let program_v2 = Program::from_str( + r" +program basic.aleo; +function foo: +function bar: +function baz: +constructor: + assert.eq true true; + ", + )?; + + // Using the off-chain VM, generate a sequence of deployments. + let deployment_v0_pass = off_chain_vm.deploy(&caller_private_key, &program_v0, None, 0, None, rng)?; + off_chain_vm.process().write().add_program(&program_v0)?; + let deployment_v1_fail = off_chain_vm.deploy(&caller_private_key, &program_v1, None, 0, None, rng)?; + let deployment_v1_pass = off_chain_vm.deploy(&caller_private_key, &program_v1, None, 0, None, rng)?; + let deployment_v2_as_v1_fail = off_chain_vm.deploy(&caller_private_key, &program_v2_as_v1, None, 0, None, rng)?; + off_chain_vm.process().write().add_program(&program_v1)?; + let deployment_v2_fail = off_chain_vm.deploy(&caller_private_key, &program_v2, None, 0, None, rng)?; + let deployment_v2_pass = off_chain_vm.deploy(&caller_private_key, &program_v2, None, 0, None, rng)?; + + // Deploy the programs to the on-chain VM individually in the following sequence: + // - deployment_v1_fail + // - deployment_v0_pass + // - deployment_v2_fail + // - deployment_v1_pass + // - deployment_v2_as_v1_fail + // - deployment_v2_pass + // Their name indicate whether the deployment should pass or fail. + + // This deployment should fail because the it is not the zero-th edition. + let block = sample_next_block(&on_chain_vm, &caller_private_key, &[deployment_v1_fail], rng)?; + assert_eq!(block.transactions().num_accepted(), 0); + assert_eq!(block.transactions().num_rejected(), 0); + assert_eq!(block.aborted_transaction_ids().len(), 1); + on_chain_vm.add_next_block(&block)?; + + // This deployment should pass. + let block = sample_next_block(&on_chain_vm, &caller_private_key, &[deployment_v0_pass], rng)?; + assert_eq!(block.transactions().num_accepted(), 1); + assert_eq!(block.transactions().num_rejected(), 0); + assert_eq!(block.aborted_transaction_ids().len(), 0); + on_chain_vm.add_next_block(&block)?; + let stack = on_chain_vm.process().read().get_stack("basic.aleo")?; + assert_eq!(*stack.program_edition(), 0); + + // This deployment should fail because it does not increment the edition. + let block = sample_next_block(&on_chain_vm, &caller_private_key, &[deployment_v2_fail], rng)?; + assert_eq!(block.transactions().num_accepted(), 0); + assert_eq!(block.transactions().num_rejected(), 0); + assert_eq!(block.aborted_transaction_ids().len(), 1); + on_chain_vm.add_next_block(&block)?; + + // This deployment should pass. + let block = sample_next_block(&on_chain_vm, &caller_private_key, &[deployment_v1_pass], rng)?; + assert_eq!(block.transactions().num_accepted(), 1); + assert_eq!(block.transactions().num_rejected(), 0); + assert_eq!(block.aborted_transaction_ids().len(), 0); + on_chain_vm.add_next_block(&block)?; + let stack = on_chain_vm.process().read().get_stack("basic.aleo")?; + assert_eq!(*stack.program_edition(), 1); + + // This deployment should fail because it attempt to redeploy at the same edition. + let block = sample_next_block(&on_chain_vm, &caller_private_key, &[deployment_v2_as_v1_fail], rng)?; + assert_eq!(block.transactions().num_accepted(), 0); + assert_eq!(block.transactions().num_rejected(), 0); + assert_eq!(block.aborted_transaction_ids().len(), 1); + on_chain_vm.add_next_block(&block)?; + + // This deployment should pass. + let block = sample_next_block(&on_chain_vm, &caller_private_key, &[deployment_v2_pass], rng)?; + assert_eq!(block.transactions().num_accepted(), 1); + assert_eq!(block.transactions().num_rejected(), 0); + assert_eq!(block.aborted_transaction_ids().len(), 0); + on_chain_vm.add_next_block(&block)?; + let stack = on_chain_vm.process().read().get_stack("basic.aleo")?; + assert_eq!(*stack.program_edition(), 2); + + Ok(()) +} + +// This test checks that: +// - records created before an upgrade are still valid after an upgrade. +// - records created after an upgrade can be created and used in the upgraded program. +// - records are semantically distinct (old records cannot be used in functions that require new records). +// - functions can be disabled using `assert.neq self.caller self.caller`. +#[test] +fn test_upgrade_with_records() -> Result<()> { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + let caller_view_key = ViewKey::try_from(&caller_private_key)?; + + // Initialize the VM. + let vm = sample_vm_at_height(CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V9)?, rng); + + // Define the two versions of the program. + let program_v0 = Program::from_str( + r" +program record_test.aleo; + +record data_v1: + owner as address.private; + data as u8.public; + +function mint: + input r0 as u8.public; + cast self.caller r0 into r1 as data_v1.record; + output r1 as data_v1.record; + +constructor: + assert.eq true true; + ", + )?; + + let program_v1 = Program::from_str( + r" +program record_test.aleo; + +record data_v1: + owner as address.private; + data as u8.public; + +record data_v2: + owner as address.private; + data as u8.public; + +function mint: + input r0 as u8.public; + assert.neq self.caller self.caller; + cast self.caller r0 into r1 as data_v1.record; + output r1 as data_v1.record; + +function convert: + input r0 as data_v1.record; + cast r0.owner r0.data into r1 as data_v2.record; + output r1 as data_v2.record; + +function burn: + input r0 as data_v2.record; + +constructor: + assert.eq true true; + ", + )?; + + // Deploy the first version of the program. + let transaction = vm.deploy(&caller_private_key, &program_v0, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + 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)?; + + // Execute the mint function twice. + let mint_execution_0 = vm.execute( + &caller_private_key, + ("record_test.aleo", "mint"), + vec![Value::from_str("0u8")?].into_iter(), + None, + 0, + None, + rng, + )?; + let mint_execution_1 = vm.execute( + &caller_private_key, + ("record_test.aleo", "mint"), + vec![Value::from_str("1u8")?].into_iter(), + None, + 0, + None, + rng, + )?; + let block = sample_next_block(&vm, &caller_private_key, &[mint_execution_0, mint_execution_1], rng)?; + assert_eq!(block.transactions().num_accepted(), 2); + assert_eq!(block.transactions().num_rejected(), 0); + assert_eq!(block.aborted_transaction_ids().len(), 0); + let mut v1_records = block + .records() + .map(|(_, record)| record.decrypt(&caller_view_key)) + .collect::>>>>()?; + assert_eq!(v1_records.len(), 2); + vm.add_next_block(&block)?; + + // Update the program. + let transaction = vm.deploy(&caller_private_key, &program_v1, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + 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)?; + + // Attempt to execute the mint function. + assert!( + vm.execute( + &caller_private_key, + ("record_test.aleo", "mint"), + vec![Value::from_str("0u8")?].into_iter(), + None, + 0, + None, + rng + ) + .is_err() + ); + + // Get the first record and execute the convert function. + let record = v1_records.pop().unwrap(); + let convert_execution = vm.execute( + &caller_private_key, + ("record_test.aleo", "convert"), + vec![Value::Record(record)].into_iter(), + None, + 0, + None, + rng, + )?; + let block = sample_next_block(&vm, &caller_private_key, &[convert_execution], rng)?; + assert_eq!(block.transactions().num_accepted(), 1); + assert_eq!(block.transactions().num_rejected(), 0); + assert_eq!(block.aborted_transaction_ids().len(), 0); + let mut v2_records = block + .records() + .map(|(_, record)| record.decrypt(&caller_view_key)) + .collect::>>>>()?; + assert_eq!(v2_records.len(), 1); + vm.add_next_block(&block)?; + + // Get the v2 record and execute the burn function. + let record = v2_records.pop().unwrap(); + let burn_execution = vm.execute( + &caller_private_key, + ("record_test.aleo", "burn"), + vec![Value::Record(record)].into_iter(), + None, + 0, + None, + rng, + )?; + let block = sample_next_block(&vm, &caller_private_key, &[burn_execution], rng)?; + 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)?; + + // Attempt to execute the burn function with the remaining v1 record. + let record = v1_records.pop().unwrap(); + assert!( + vm.execute( + &caller_private_key, + ("record_test.aleo", "burn"), + vec![Value::Record(record)].into_iter(), + None, + 0, + None, + rng + ) + .is_err() + ); + + Ok(()) +} + +// This test checks that: +// - mappings created before an upgrade are still valid after an upgrade. +// - mappings created by and upgraded are correctly initialized and usable in the program. +// - functions can be disabled by inserting a failing condition in the on-chain logic. +#[test] +fn test_upgrade_with_mappings() -> Result<()> { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + + // Initialize the VM. + let vm = sample_vm_at_height(CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V9)?, rng); + + // Define the two versions of the program. + let program_v0 = Program::from_str( + r" +program mapping_test.aleo; + +mapping data_v1: + key as u8.public; + value as u8.public; + +function store_data_v1: + input r0 as u8.public; + input r1 as u8.public; + async store_data_v1 r0 r1 into r2; + output r2 as mapping_test.aleo/store_data_v1.future; +finalize store_data_v1: + input r0 as u8.public; + input r1 as u8.public; + set r1 into data_v1[r0]; + +constructor: + assert.eq true true; + ", + )?; + + let program_v1 = Program::from_str( + r" +program mapping_test.aleo; + +mapping data_v1: + key as u8.public; + value as u8.public; + +mapping data_v2: + key as u8.public; + value as u8.public; + +function store_data_v1: + input r0 as u8.public; + input r1 as u8.public; + async store_data_v1 r0 r1 into r2; + output r2 as mapping_test.aleo/store_data_v1.future; +finalize store_data_v1: + input r0 as u8.public; + input r1 as u8.public; + assert.neq true true; + +function migrate_data_v1_to_v2: + input r0 as u8.public; + async migrate_data_v1_to_v2 r0 into r1; + output r1 as mapping_test.aleo/migrate_data_v1_to_v2.future; +finalize migrate_data_v1_to_v2: + input r0 as u8.public; + get data_v1[r0] into r1; + remove data_v1[r0]; + set r1 into data_v2[r0]; + +function store_data_v2: + input r0 as u8.public; + input r1 as u8.public; + async store_data_v2 r0 r1 into r2; + output r2 as mapping_test.aleo/store_data_v2.future; +finalize store_data_v2: + input r0 as u8.public; + input r1 as u8.public; + set r1 into data_v2[r0]; + +constructor: + assert.eq true true; + ", + )?; + + // Deploy the first version of the program. + let transaction = vm.deploy(&caller_private_key, &program_v0, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + 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)?; + + // Execute the store_data_v1 function. + let store_data_v1_execution = vm.execute( + &caller_private_key, + ("mapping_test.aleo", "store_data_v1"), + vec![Value::from_str("0u8")?, Value::from_str("0u8")?].into_iter(), + None, + 0, + None, + rng, + )?; + let block = sample_next_block(&vm, &caller_private_key, &[store_data_v1_execution], rng)?; + 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)?; + + // Check that the value was stored correctly. + let value = match vm.finalize_store().get_value_confirmed( + ProgramID::from_str("mapping_test.aleo")?, + Identifier::from_str("data_v1")?, + &Plaintext::from_str("0u8")?, + )? { + Some(Value::Plaintext(Plaintext::Literal(Literal::U8(value), _))) => *value, + value => bail!(format!("Unexpected value: {:?}", value)), + }; + assert_eq!(value, 0u8); + + // Update the program. + let transaction = vm.deploy(&caller_private_key, &program_v1, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + 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)?; + + // Attempt to execute the store_data_v1 function. + let transaction = vm.execute( + &caller_private_key, + ("mapping_test.aleo", "store_data_v1"), + vec![Value::from_str("1u8")?, Value::from_str("1u8")?].into_iter(), + None, + 0, + None, + rng, + )?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + assert_eq!(block.transactions().num_accepted(), 0); + assert_eq!(block.transactions().num_rejected(), 1); + assert_eq!(block.aborted_transaction_ids().len(), 0); + vm.add_next_block(&block)?; + + // Execute the migrate_data_v1_to_v2 function. + let migrate_data_v1_to_v2_execution = vm.execute( + &caller_private_key, + ("mapping_test.aleo", "migrate_data_v1_to_v2"), + vec![Value::from_str("0u8")?].into_iter(), + None, + 0, + None, + rng, + )?; + let block = sample_next_block(&vm, &caller_private_key, &[migrate_data_v1_to_v2_execution], rng)?; + 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)?; + + // Check that the value was migrated correctly. + let value = match vm.finalize_store().get_value_confirmed( + ProgramID::from_str("mapping_test.aleo")?, + Identifier::from_str("data_v2")?, + &Plaintext::from_str("0u8")?, + )? { + Some(Value::Plaintext(Plaintext::Literal(Literal::U8(value), _))) => *value, + value => bail!(format!("Unexpected value: {:?}", value)), + }; + assert_eq!(value, 0u8); + + // Check that the old value was removed. + assert!( + vm.finalize_store() + .get_value_confirmed( + ProgramID::from_str("mapping_test.aleo")?, + Identifier::from_str("data_v1")?, + &Plaintext::from_str("0u8")? + )? + .is_none() + ); + + // Execute the store_data_v2 function. + let store_data_v2_execution = vm.execute( + &caller_private_key, + ("mapping_test.aleo", "store_data_v2"), + vec![Value::from_str("1u8")?, Value::from_str("1u8")?].into_iter(), + None, + 0, + None, + rng, + )?; + let block = sample_next_block(&vm, &caller_private_key, &[store_data_v2_execution], rng)?; + 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)?; + + // Check that the value was stored correctly. + let value = match vm.finalize_store().get_value_confirmed( + ProgramID::from_str("mapping_test.aleo")?, + Identifier::from_str("data_v2")?, + &Plaintext::from_str("1u8")?, + )? { + Some(Value::Plaintext(Plaintext::Literal(Literal::U8(value), _))) => *value, + value => bail!(format!("Unexpected value: {:?}", value)), + }; + assert_eq!(value, 1u8); + + Ok(()) +} + +// This test checks that: +// - a dependent program accepts an upgrade to off-chain logic +// - a dependent program accepts an upgrade to on-chain logic +// - a dependent program can fix a specific version of the dependency +// - old executions of the dependent program are no longer valid after an upgrade +#[test] +fn test_upgrade_with_dependents() -> Result<()> { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + + // Initialize the VM. + let vm = sample_vm_at_height(CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V9)?, rng); + + // Define the two versions of the dependency program. + let dependency_v0 = Program::from_str( + r" +program dependency.aleo; + +function sum: + input r0 as u8.public; + input r1 as u8.public; + add r0 r1 into r2; + output r0 as u8.public; + +function sum_and_check: + input r0 as u8.public; + input r1 as u8.public; + add r0 r1 into r2; + async sum_and_check into r3; + output r2 as u8.public; + output r3 as dependency.aleo/sum_and_check.future; +finalize sum_and_check: + assert.eq true true; + +constructor: + assert.eq true true; + ", + )?; + + let dependency_v1 = Program::from_str( + r" +program dependency.aleo; + +function sum: + input r0 as u8.public; + input r1 as u8.public; + add.w r0 r1 into r2; + output r0 as u8.public; + +function sum_and_check: + input r0 as u8.public; + input r1 as u8.public; + add.w r0 r1 into r2; + async sum_and_check into r3; + output r2 as u8.public; + output r3 as dependency.aleo/sum_and_check.future; +finalize sum_and_check: + assert.eq true false; + +constructor: + assert.eq true true; + ", + )?; + + // Define the two versions of the dependent program. + let dependent_v0 = Program::from_str( + r" +import dependency.aleo; + +program dependent.aleo; + +function sum_unchecked: + input r0 as u8.public; + input r1 as u8.public; + call dependency.aleo/sum r0 r1 into r2; + output r2 as u8.public; + +function sum: + input r0 as u8.public; + input r1 as u8.public; + call dependency.aleo/sum r0 r1 into r2; + async sum into r3; + output r2 as u8.public; + output r3 as dependent.aleo/sum.future; +finalize sum: + assert.eq dependency.aleo/edition 0u16; + +function sum_and_check: + input r0 as u8.public; + input r1 as u8.public; + call dependency.aleo/sum_and_check r0 r1 into r2 r3; + async sum_and_check r3 into r4; + output r2 as u8.public; + output r4 as dependent.aleo/sum_and_check.future; +finalize sum_and_check: + input r0 as dependency.aleo/sum_and_check.future; + await r0; + +constructor: + assert.eq true true; + ", + )?; + + let dependent_v1 = Program::from_str( + r" +import dependency.aleo; + +program dependent.aleo; + +function sum_unchecked: + input r0 as u8.public; + input r1 as u8.public; + call dependency.aleo/sum r0 r1 into r2; + output r2 as u8.public; + +function sum: + input r0 as u8.public; + input r1 as u8.public; + call dependency.aleo/sum r0 r1 into r2; + async sum into r3; + output r2 as u8.public; + output r3 as dependent.aleo/sum.future; +finalize sum: + assert.eq dependency.aleo/edition 1u16; + +function sum_and_check: + input r0 as u8.public; + input r1 as u8.public; + call dependency.aleo/sum_and_check r0 r1 into r2 r3; + async sum_and_check r3 into r4; + output r2 as u8.public; + output r4 as dependent.aleo/sum_and_check.future; +finalize sum_and_check: + input r0 as dependency.aleo/sum_and_check.future; + await r0; + +constructor: + assert.eq true true; + ", + )?; + + // At a high level, this test will: + // 1. Deploy the v0 dependency and v0 dependent. + // 2. Verify that the the dependent program can be correctly executed. + // 3. Update the dependency to v1. + // 4. Verify that the call to `sum_and_check` automatically uses the new logic, however, the call `sum` fails because the edition is not 0. + // 5. Update the dependent to v1. + // 6. Verify that the call to `sum` now passes because the edition is 1. + + // Deploy the v0 dependency. + let transaction = vm.deploy(&caller_private_key, &dependency_v0, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + 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)?; + + // Deploy the v0 dependent. + let transaction = vm.deploy(&caller_private_key, &dependent_v0, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + 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)?; + + // Execute the functions. + let tx_1 = vm.execute( + &caller_private_key, + ("dependent.aleo", "sum"), + vec![Value::from_str("1u8")?, Value::from_str("1u8")?].into_iter(), + None, + 0, + None, + rng, + )?; + let tx_2 = vm.execute( + &caller_private_key, + ("dependent.aleo", "sum_and_check"), + vec![Value::from_str("1u8")?, Value::from_str("1u8")?].into_iter(), + None, + 0, + None, + rng, + )?; + let block = sample_next_block(&vm, &caller_private_key, &[tx_1, tx_2], rng)?; + assert_eq!(block.transactions().num_accepted(), 2); + assert_eq!(block.transactions().num_rejected(), 0); + assert_eq!(block.aborted_transaction_ids().len(), 0); + vm.add_next_block(&block)?; + + // Verify that the sum function fails on overflow. + let result = std::panic::catch_unwind(AssertUnwindSafe(|| { + vm.execute( + &caller_private_key, + ("dependent.aleo", "sum"), + vec![Value::from_str("255u8")?, Value::from_str("1u8")?].into_iter(), + None, + 0, + None, + rng, + ) + })); + assert!(result.is_err()); + + // Get a valid execution before the dependency upgrade. + let sum_unchecked = vm.execute( + &caller_private_key, + ("dependent.aleo", "sum_unchecked"), + vec![Value::from_str("1u8")?, Value::from_str("1u8")?].into_iter(), + None, + 0, + None, + rng, + )?; + assert!(vm.check_transaction(&sum_unchecked, None, rng).is_ok()); + + // Update the dependency to v1. + let transaction = vm.deploy(&caller_private_key, &dependency_v1, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + 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)?; + + // Verify that the original sum transaction fails after the dependency upgrade. + assert!(vm.check_transaction(&sum_unchecked, None, rng).is_err()); + let block = sample_next_block(&vm, &caller_private_key, &[sum_unchecked], rng)?; + assert_eq!(block.transactions().num_accepted(), 0); + assert_eq!(block.transactions().num_rejected(), 0); + assert_eq!(block.aborted_transaction_ids().len(), 1); + vm.add_next_block(&block)?; + + // Verify that the sum function fails on edition check. + let tx_1 = vm.execute( + &caller_private_key, + ("dependent.aleo", "sum"), + vec![Value::from_str("1u8")?, Value::from_str("1u8")?].into_iter(), + None, + 0, + None, + rng, + )?; + let tx_2 = vm.execute( + &caller_private_key, + ("dependent.aleo", "sum_and_check"), + vec![Value::from_str("1u8")?, Value::from_str("1u8")?].into_iter(), + None, + 0, + None, + rng, + )?; + let block = sample_next_block(&vm, &caller_private_key, &[tx_1, tx_2], rng)?; + assert_eq!(block.transactions().num_accepted(), 0); + assert_eq!(block.transactions().num_rejected(), 2); + assert_eq!(block.aborted_transaction_ids().len(), 0); + vm.add_next_block(&block)?; + + // Update the dependent to v1. + let transaction = vm.deploy(&caller_private_key, &dependent_v1, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + 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)?; + + // Verify that the sum function passes. + let tx_1 = vm.execute( + &caller_private_key, + ("dependent.aleo", "sum"), + vec![Value::from_str("1u8")?, Value::from_str("1u8")?].into_iter(), + None, + 0, + None, + rng, + )?; + let tx_2 = vm.execute( + &caller_private_key, + ("dependent.aleo", "sum"), + vec![Value::from_str("255u8")?, Value::from_str("1u8")?].into_iter(), + None, + 0, + None, + rng, + )?; + let block = sample_next_block(&vm, &caller_private_key, &[tx_1, tx_2], rng)?; + assert_eq!(block.transactions().num_accepted(), 2); + assert_eq!(block.transactions().num_rejected(), 0); + assert_eq!(block.aborted_transaction_ids().len(), 0); + vm.add_next_block(&block)?; + + Ok(()) +} + +// This test checks that a deployment with a failing _init block is rejected. +#[test] +fn test_failing_init_block() -> Result<()> { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + + // Initialize the VM. + let vm = sample_vm_at_height(CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V9)?, rng); + + // Define the programs. + let passing_program = Program::from_str( + r" +program hello1.aleo; + +function foo: + input r0 as u8.public; + output r0 as u8.public; + +constructor: + assert.eq true true; + ", + )?; + + let failing_program = Program::from_str( + r" +program hello2.aleo; + +function foo: + input r0 as u8.public; + output r0 as u8.public; + +constructor: + assert.eq true false; + ", + )?; + + // Deploy the passing program. + let transaction = vm.deploy(&caller_private_key, &passing_program, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + 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)?; + + // Deploy the failing program. + let transaction = vm.deploy(&caller_private_key, &failing_program, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + assert_eq!(block.transactions().num_accepted(), 0); + assert_eq!(block.transactions().num_rejected(), 1); + assert_eq!(block.aborted_transaction_ids().len(), 0); + vm.add_next_block(&block)?; + + Ok(()) +} + +// This test verifies that anyone can upgrade a program whose that explicitly places no restrictions on upgrades in the constructor. +#[test] +fn test_anyone_can_upgrade() -> Result<()> { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + + // Initialize unrelated callers. + let unrelated_caller_private_key_0 = PrivateKey::new(rng)?; + let unrelated_caller_address_0 = Address::try_from(&unrelated_caller_private_key_0)?; + let unrelated_caller_private_key_1 = PrivateKey::new(rng)?; + let unrelated_caller_address_1 = Address::try_from(&unrelated_caller_private_key_1)?; + + // Initialize the VM. + let vm = sample_vm_at_height(CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V9)?, rng); + + // Fund the unrelated callers. + let transfer_1 = vm.execute( + &caller_private_key, + ("credits.aleo", "transfer_public"), + vec![Value::from_str(&format!("{unrelated_caller_address_0}"))?, Value::from_str("1_000_000_000_000u64")?] + .into_iter(), + None, + 0, + None, + rng, + )?; + let transfer_2 = vm.execute( + &caller_private_key, + ("credits.aleo", "transfer_public"), + vec![Value::from_str(&format!("{unrelated_caller_address_1}"))?, Value::from_str("1_000_000_000_000u64")?] + .into_iter(), + None, + 0, + None, + rng, + )?; + let block = sample_next_block(&vm, &caller_private_key, &[transfer_1, transfer_2], rng)?; + assert_eq!(block.transactions().num_accepted(), 2); + assert_eq!(block.transactions().num_rejected(), 0); + assert_eq!(block.aborted_transaction_ids().len(), 0); + vm.add_next_block(&block)?; + + // Define the programs. + let program_v0 = Program::from_str( + r" +program upgradable.aleo; +function foo: +constructor: + assert.eq true true; + ", + )?; + + let program_v1 = Program::from_str( + r" +program upgradable.aleo; +function foo: +function bar: +constructor: + assert.eq true true; + ", + )?; + + let program_v2 = Program::from_str( + r" +program upgradable.aleo; +function foo: +function bar: +function baz: +constructor: + assert.eq true true; + ", + )?; + + // Deploy the first version of the program. + let transaction = vm.deploy(&caller_private_key, &program_v0, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + 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)?; + + // Deploy the second version of the program. + let transaction = vm.deploy(&unrelated_caller_private_key_0, &program_v1, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + 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)?; + + // Deploy the third version of the program. + let transaction = vm.deploy(&unrelated_caller_private_key_1, &program_v2, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + 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)?; + + Ok(()) +} + +// This test checks that a program the fixes the expected edition cannot be upgraded. +#[test] +fn test_non_upgradable_programs() -> Result<()> { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + + // Initialize the VM. + let vm = sample_vm_at_height(CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V9)?, rng); + + let program_1_v0 = Program::from_str( + r" +program non_upgradable_1.aleo; +function foo: +constructor: + assert.eq edition 0u16; + ", + )?; + + let program_1_v1 = Program::from_str( + r" +program non_upgradable_1.aleo; +function foo: +function bar: +constructor: + assert.eq edition 0u16; + ", + )?; + + // Deploy the program and then upgrade. The upgrade should fail to be finalized. + let transaction = vm.deploy(&caller_private_key, &program_1_v0, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + 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)?; + + let transaction = vm.deploy(&caller_private_key, &program_1_v1, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + assert_eq!(block.transactions().num_accepted(), 0); + assert_eq!(block.transactions().num_rejected(), 1); + assert_eq!(block.aborted_transaction_ids().len(), 0); + vm.add_next_block(&block)?; + + Ok(()) +} + +// This test checks that a program can be made non-upgradable after being upgradable. +#[test] +fn test_downgrade_upgradable_program() -> Result<()> { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + + // Initialize the VM. + let vm = sample_vm_at_height(CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V9)?, rng); + + // Define the programs. + let program_v0 = Program::from_str( + r" +program upgradable.aleo; +mapping locked: + key as boolean.public; + value as boolean.public; +function set_lock: + async set_lock into r0; + output r0 as upgradable.aleo/set_lock.future; +finalize set_lock: + set true into locked[true]; +function foo: +constructor: + contains locked[true] into r0; + assert.eq r0 false; + ", + )?; + + let program_v1 = Program::from_str( + r" +program upgradable.aleo; +mapping locked: + key as boolean.public; + value as boolean.public; +function set_lock: + async set_lock into r0; + output r0 as upgradable.aleo/set_lock.future; +finalize set_lock: + set true into locked[true]; +function foo: +function bar: +constructor: + contains locked[true] into r0; + assert.eq r0 false; + ", + )?; + + let program_v2 = Program::from_str( + r" +program upgradable.aleo; +mapping locked: + key as boolean.public; + value as boolean.public; +function set_lock: + async set_lock into r0; + output r0 as upgradable.aleo/set_lock.future; +finalize set_lock: + set true into locked[true]; +function foo: +function bar: +function baz: +constructor: + contains locked[true] into r0; + assert.eq r0 false; + ", + )?; + + // Deploy the first version of the program. + let transaction = vm.deploy(&caller_private_key, &program_v0, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + 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)?; + + // Deploy the second version of the program. + let transaction = vm.deploy(&caller_private_key, &program_v1, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + 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)?; + + // Set the lock. + let transaction = vm.execute( + &caller_private_key, + ("upgradable.aleo", "set_lock"), + Vec::>::new().into_iter(), + None, + 0, + None, + rng, + )?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + 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)?; + + // Attempt to deploy the third version of the program. + let transaction = vm.deploy(&caller_private_key, &program_v2, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + assert_eq!(block.transactions().num_accepted(), 0); + assert_eq!(block.transactions().num_rejected(), 1); + assert_eq!(block.aborted_transaction_ids().len(), 0); + vm.add_next_block(&block)?; + + Ok(()) +} + +// This test checks that an upgrade can be locked to a checksum. +// The checksum is managed by an admin address. +#[test] +fn test_lock_upgrade_to_checksum() -> Result<()> { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + let caller_address = Address::try_from(&caller_private_key)?; + + // Initialize the VM. + let vm = sample_vm_at_height(CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V9)?, rng); + + // Define the programs. + let program_v0 = Program::from_str(&format!( + r" +program locked_upgrade.aleo; +mapping admin: + key as boolean.public; + value as address.public; +mapping expected_checksum: + key as boolean.public; + value as [u8; 32u32].public; +function set_expected: + input r0 as [u8; 32u32].public; + async set_expected self.caller r0 into r1; + output r1 as locked_upgrade.aleo/set_expected.future; +finalize set_expected: + input r0 as address.public; + input r1 as [u8; 32u32].public; + get admin[true] into r2; + assert.eq r0 r2; + set r1 into expected_checksum[true]; +constructor: + branch.neq edition 0u16 to rest; + set {caller_address} into admin[true]; + branch.eq true true to end; + position rest; + get expected_checksum[true] into r0; + assert.eq r0 checksum; + position end; + " + ))?; + + let program_v1 = Program::from_str(&format!( + r" +program locked_upgrade.aleo; +mapping admin: + key as boolean.public; + value as address.public; +mapping expected_checksum: + key as boolean.public; + value as [u8; 32u32].public; +function bar: +function set_expected: + input r0 as [u8; 32u32].public; + async set_expected self.caller r0 into r1; + output r1 as locked_upgrade.aleo/set_expected.future; +finalize set_expected: + input r0 as address.public; + input r1 as [u8; 32u32].public; + get admin[true] into r2; + assert.eq r0 r2; + set r1 into expected_checksum[true]; +constructor: + branch.neq edition 0u16 to rest; + set {caller_address} into admin[true]; + branch.eq true true to end; + position rest; + get expected_checksum[true] into r0; + assert.eq r0 checksum; + position end; + " + ))?; + + let program_v1_mismatch = Program::from_str(&format!( + r" +program locked_upgrade.aleo; +mapping admin: + key as boolean.public; + value as address.public; +mapping expected_checksum: + key as boolean.public; + value as [u8; 32u32].public; +function baz: +function set_expected: + input r0 as [u8; 32u32].public; + async set_expected self.caller r0 into r1; + output r1 as locked_upgrade.aleo/set_expected.future; +finalize set_expected: + input r0 as address.public; + input r1 as [u8; 32u32].public; + get admin[true] into r2; + assert.eq r0 r2; + set r1 into expected_checksum[true]; +constructor: + branch.neq edition 0u16 to rest; + set {caller_address} into admin[true]; + branch.eq true true to end; + position rest; + get expected_checksum[true] into r0; + assert.eq r0 checksum; + position end; + " + ))?; + + // Deploy the first version of the program. + let transaction = vm.deploy(&caller_private_key, &program_v0, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + 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)?; + + // Check that the caller is the admin. + let Some(Value::Plaintext(Plaintext::Literal(Literal::Address(admin), _))) = + vm.finalize_store().get_value_confirmed( + ProgramID::from_str("locked_upgrade.aleo")?, + Identifier::from_str("admin")?, + &Plaintext::from_str("true")?, + )? + else { + bail!("Unexpected entry in admin mapping"); + }; + assert_eq!(admin, caller_address); + + // Attempt to upgrade without setting the expected checksum. + let transaction = vm.deploy(&caller_private_key, &program_v1, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + assert_eq!(block.transactions().num_accepted(), 0); + assert_eq!(block.transactions().num_rejected(), 1); + assert_eq!(block.aborted_transaction_ids().len(), 0); + vm.add_next_block(&block)?; + + // Attempt to set the expected checksum with the wrong admin. + let checksum = Value::from_str( + r"[ + 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, + 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, + 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, + 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8 + ]", + )?; + let admin_private_key = PrivateKey::new(rng)?; + let transaction = vm.execute( + &admin_private_key, + ("locked_upgrade.aleo", "set_expected"), + vec![checksum].into_iter(), + None, + 0, + None, + rng, + )?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + assert_eq!(block.transactions().num_accepted(), 0); + assert_eq!(block.transactions().num_rejected(), 0); + assert_eq!(block.aborted_transaction_ids().len(), 1); + vm.add_next_block(&block)?; + + // Check that there is no expected checksum set. + assert!( + vm.finalize_store() + .get_value_confirmed( + ProgramID::from_str("locked_upgrade.aleo")?, + Identifier::from_str("expected_checksum")?, + &Plaintext::from_str("true")?, + )? + .is_none() + ); + + // Set the expected checksum. + let checksum = program_v1.to_checksum(); + let transaction = vm.execute( + &caller_private_key, + ("locked_upgrade.aleo", "set_expected"), + vec![Value::from_str(&format!("[{}]", checksum.iter().join(", ")))].into_iter(), + None, + 0, + None, + rng, + )?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + 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)?; + + // Check that the expected checksum is set. + let Some(Value::Plaintext(expected)) = vm.finalize_store().get_value_confirmed( + ProgramID::from_str("locked_upgrade.aleo")?, + Identifier::from_str("expected_checksum")?, + &Plaintext::from_str("true")?, + )? + else { + bail!("Unexpected entry in expected_checksum mapping"); + }; + assert_eq!(Plaintext::from(checksum), expected); + + // Attempt to upgrade with a mismatched program. + let transaction = vm.deploy(&caller_private_key, &program_v1_mismatch, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + assert_eq!(block.transactions().num_accepted(), 0); + assert_eq!(block.transactions().num_rejected(), 1); + assert_eq!(block.aborted_transaction_ids().len(), 0); + vm.add_next_block(&block)?; + + // Update with the expected checksum set. + let transaction = vm.deploy(&caller_private_key, &program_v1, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + 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)?; + + Ok(()) +} + +#[test] +fn test_upgrade_without_changing_contents_passes() -> Result<()> { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + + // Initialize the VM. + let vm = sample_vm_at_height(CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V9)?, rng); + + // Define the program. + let program_v0 = Program::from_str( + r" +program upgradable.aleo; +constructor: + assert.eq true true; +function dummy:", + )?; + + // Define a variant of the program that contains an extra mapping. + let program_v1 = Program::from_str( + r" +program upgradable.aleo; +constructor: + assert.eq true true; +mapping foo: + key as boolean.public; + value as boolean.public; +function dummy:", + )?; + + // Construct the first deployment. + let transaction_first = vm.deploy(&caller_private_key, &program_v0, None, 0, None, rng)?; + let deployment_first = transaction_first.deployment().unwrap(); + assert_eq!(deployment_first.edition(), 0); + let block = sample_next_block(&vm, &caller_private_key, &[transaction_first], rng)?; + 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)?; + + // Deploy the program again without changing its contents. + let transaction = vm.deploy(&caller_private_key, &program_v0, None, 0, None, rng)?; + let deployment = transaction.deployment().unwrap(); + assert_eq!(deployment.edition(), 1); + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + 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)?; + + // Deploy the program with an extra mapping as an upgrade. + let transaction_third = vm.deploy(&caller_private_key, &program_v1, None, 0, None, rng)?; + let deployment_third = transaction_third.deployment().unwrap(); + assert_eq!(deployment_third.edition(), 2); + let block = sample_next_block(&vm, &caller_private_key, &[transaction_third], rng)?; + assert_eq!(block.transactions().num_accepted(), 1); + assert_eq!(block.transactions().num_rejected(), 0); + assert_eq!(block.aborted_transaction_ids().len(), 0); + + Ok(()) +} + +// This test verifies that the `credits` program is not upgradable. +#[test] +fn test_credits_is_not_upgradable() { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + + // Initialize the VM. + let vm = sample_vm_at_height(CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V9).unwrap(), rng); + + // Add a function to the credits program. + let credits_program = Program::::credits().unwrap(); + let program = Program::from_str(&format!("{credits_program}\nfunction dummy:")).unwrap(); + + // Attempt to deploy the program. + assert!(vm.deploy(&caller_private_key, &program, None, 0, None, rng).is_err()); +} + +// This test verifies that programs that were deployed before the upgrade cannot be upgraded. +#[test] +fn test_existing_programs_cannot_be_upgraded() -> Result<()> { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + + // Initialize the VM. + let vm = sample_vm_at_height(CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V9)? - 2, rng); + + // Define the programs. + let program_0_v0 = Program::from_str( + r" +program test_program_one.aleo; +function dummy:", + )?; + + let program_1_v0 = Program::from_str( + r" +program test_program_two.aleo; +function dummy:", + )?; + + let program_0_v1_without_constructor = Program::from_str( + r" +program test_program_one.aleo; +function dummy: +function dummy_2:", + )?; + + let program_0_v1_with_failing_constructor = Program::from_str( + r" +program test_program_one.aleo; +function dummy: +function dummy_2: +constructor: + assert.eq edition 0u16;", + )?; + + let program_0_v1_valid = Program::from_str( + r" +program test_program_one.aleo; +function dummy: +function dummy_2: +constructor: + assert.eq edition 1u16;", + )?; + + let program_0_v2_fails = Program::from_str( + r" +program test_program_one.aleo; +function dummy: +function dummy_2: +function dummy_3: +constructor: + assert.eq edition 1u16;", + )?; + + let program_1_v1_valid = Program::from_str( + r" +program test_program_two.aleo; +function dummy: +function dummy_2: +constructor: + assert.eq true true;", + )?; + + // Deploy the v0 versions of the programs. + let transaction = vm.deploy(&caller_private_key, &program_0_v0, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + 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)?; + + let transaction = vm.deploy(&caller_private_key, &program_1_v0, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + 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)?; + + // Assert that the VM is after the V9 height. + assert_eq!(vm.block_store().current_block_height(), CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V9)?); + + // Attempt to upgrade the first program. + let result = vm.deploy(&caller_private_key, &program_0_v1_without_constructor, None, 0, None, rng); + assert!(result.is_err()); + + // Attempt to upgrade the first program. + let result = vm.deploy(&caller_private_key, &program_0_v1_with_failing_constructor, None, 0, None, rng); + assert!(result.is_err()); + + // Attempt to upgrade the first program. + let result = vm.deploy(&caller_private_key, &program_0_v1_valid, None, 0, None, rng); + assert!(result.is_err()); + + // Attempt to upgrade the first program. + let result = vm.deploy(&caller_private_key, &program_0_v2_fails, None, 0, None, rng); + assert!(result.is_err()); + + // Attempt to upgrade the second program. + let result = vm.deploy(&caller_private_key, &program_1_v1_valid, None, 0, None, rng); + assert!(result.is_err()); + + Ok(()) +} + +// This test checks that a program can be upgraded using the simple admin mechanism. +#[test] +fn test_simple_admin_upgrade() { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + let caller_address = Address::try_from(&caller_private_key).unwrap(); + + // Initialize the VM. + let vm = sample_vm_at_height(CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V9).unwrap(), rng); + + // Generate a separate caller. + let separate_caller_private_key = PrivateKey::new(rng).unwrap(); + let separate_caller_address = Address::try_from(&separate_caller_private_key).unwrap(); + + // Fund the new caller with 10M credits. + let transaction = vm + .execute( + &caller_private_key, + ("credits.aleo", "transfer_public"), + vec![ + Value::from_str(&format!("{separate_caller_address}")).unwrap(), + Value::from_str("10_000_000_000_000u64").unwrap(), + ] + .into_iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + let block = sample_next_block(&vm, &caller_private_key, &[transaction], 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(); + + // Define the programs. + let program_v0 = Program::from_str(&format!( + r" +program simple_admin.aleo; +function foo: +constructor: + assert.eq program_owner {caller_address}; + " + )) + .unwrap(); + + let program_v1 = Program::from_str(&format!( + r" +program simple_admin.aleo; +function foo: +function bar: +constructor: + assert.eq program_owner {caller_address}; + " + )) + .unwrap(); + + // Attempt to deploy the first version of the program with the wrong admin. + let transaction = vm.deploy(&separate_caller_private_key, &program_v0, None, 0, None, rng).unwrap(); + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); + assert_eq!(block.transactions().num_accepted(), 0); + assert_eq!(block.transactions().num_rejected(), 1); + assert_eq!(block.aborted_transaction_ids().len(), 0); + vm.add_next_block(&block).unwrap(); + + // Deploy the first version of the program with the correct admin. + let transaction = vm.deploy(&caller_private_key, &program_v0, None, 0, None, rng).unwrap(); + let block = sample_next_block(&vm, &caller_private_key, &[transaction], 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(); + + // Attempt to upgrade the program with the wrong admin. + let transaction = vm.deploy(&separate_caller_private_key, &program_v1, None, 0, None, rng).unwrap(); + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); + assert_eq!(block.transactions().num_accepted(), 0); + assert_eq!(block.transactions().num_rejected(), 1); + assert_eq!(block.aborted_transaction_ids().len(), 0); + vm.add_next_block(&block).unwrap(); + + // Upgrade the program with the correct admin. + let transaction = vm.deploy(&caller_private_key, &program_v1, None, 0, None, rng).unwrap(); + let block = sample_next_block(&vm, &caller_private_key, &[transaction], 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(); +} + +// This test verifies the behavior of `partially_verified_transactions` cache for transactions before and after a program upgrade. +#[test] +fn test_verification_cache() { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + + // Initialize the VM. + let vm = sample_vm_at_height(CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V9).unwrap(), rng); + + // Define the programs. + let program_v0 = Program::from_str( + r" +program test_program.aleo; +function foo: + input r0 as boolean.private; + assert.eq r0 true; +constructor: + assert.eq true true; + ", + ) + .unwrap(); + + let program_v1 = Program::from_str( + r" +program test_program.aleo; +function foo: + input r0 as boolean.private; + assert.eq r0 false; +constructor: + assert.eq true true; + ", + ) + .unwrap(); + + // Deploy the first version of the program. + let transaction = vm.deploy(&caller_private_key, &program_v0, None, 0, None, rng).unwrap(); + let block = sample_next_block(&vm, &caller_private_key, &[transaction], 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(); + + // Execute a transaction with the first version of the program. + let execution = vm + .execute( + &caller_private_key, + ("test_program.aleo", "foo"), + vec![Value::from_str("true").unwrap()].into_iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + + // Get the size of the verification cache before running the verification. + let cache_size_before = vm.partially_verified_transactions().read().len(); + + // Verify the transaction and check the cache size. + assert!(vm.check_transaction(&execution, None, rng).is_ok()); + let cache_size_after = vm.partially_verified_transactions().read().len(); + assert_eq!(cache_size_after, cache_size_before + 1); + + // Upgrade the program to the new version. + let transaction = vm.deploy(&caller_private_key, &program_v1, None, 0, None, rng).unwrap(); + let block = sample_next_block(&vm, &caller_private_key, &[transaction], 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(); + + // Verify the transaction again and check the cache size. + assert!(vm.check_transaction(&execution, None, rng).is_err()); + let cache_size_after_upgrade = vm.partially_verified_transactions().read().len(); + assert_eq!(cache_size_after_upgrade, cache_size_after + 1); +} + +// This test verifies that +// - a program deployed before `V9` does not have an owner. +// - `credits.aleo` does not have an owner. +// - a program deployed after `V9` has an owner. +#[test] +fn test_program_deployed_before_v9_do_not_have_owner() { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + let caller_address = Address::try_from(&caller_private_key).unwrap(); + + // Initialize the VM. + let vm = sample_vm_at_height(CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V9).unwrap() - 1, rng); + + // Define the programs. + let program_before_v9 = Program::from_str( + r" +program test_program_0.aleo; +function foo:", + ) + .unwrap(); + + let program_after_v9 = Program::from_str( + r" +program test_program_1.aleo; +function foo: +constructor: + assert.eq true true; +", + ) + .unwrap(); + + // Deploy the first program. + let transaction = vm.deploy(&caller_private_key, &program_before_v9, None, 0, None, rng).unwrap(); + // Check that the deployment does not have an owner or checksum. + assert!(transaction.deployment().unwrap().program_checksum().is_none()); + assert!(transaction.deployment().unwrap().program_owner().is_none()); + // Create the next block and check that the transaction is accepted. + let block = sample_next_block(&vm, &caller_private_key, &[transaction], 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 transaction = vm.deploy(&caller_private_key, &program_after_v9, None, 0, None, rng).unwrap(); + // Check that the deployment has an owner and checksum. + assert!(transaction.deployment().unwrap().program_checksum().is_some()); + assert!(transaction.deployment().unwrap().program_owner().is_some()); + // Create the next block and check that the transaction is accepted. + let block = sample_next_block(&vm, &caller_private_key, &[transaction], 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(); + + // Check the owners of the programs. + let stack = vm.process().read().get_stack("credits.aleo").unwrap(); + assert!(stack.program_owner().is_none()); + + let stack = vm.process().read().get_stack("test_program_0.aleo").unwrap(); + assert!(stack.program_owner().is_none()); + + let stack = vm.process().read().get_stack("test_program_1.aleo").unwrap(); + assert!(stack.program_owner().is_some()); + assert_eq!(stack.program_owner().unwrap(), caller_address); +} + +#[test] +fn test_old_execution_is_aborted_after_upgrade() { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + + // Initialize the VM. + let vm = sample_vm_at_height(CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V9).unwrap(), rng); + + // Define the programs. + let program_v0 = Program::from_str( + r" +program test_program.aleo; +constructor: + assert.eq true true; +function dummy:", + ) + .unwrap(); + + let program_v1 = Program::from_str( + r" +program test_program.aleo; +constructor: + assert.eq true true; +function dummy: +function dummy2:", + ) + .unwrap(); + + // Deploy the first version of the program. + let transaction = vm.deploy(&caller_private_key, &program_v0, None, 0, None, rng).unwrap(); + let block = sample_next_block(&vm, &caller_private_key, &[transaction], 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(); + + // Pre-generate 3 executions of the dummy function. + let executions = (0..3) + .map(|_| { + vm.execute( + &caller_private_key, + ("test_program.aleo", "dummy"), + Vec::>::new().into_iter(), + None, + 0, + None, + rng, + ) + .unwrap() + }) + .collect::>(); + + // Add 2 transactions individually to blocks. + // They are expected to pass because the program has not been upgraded. + for execution in &executions[0..2] { + let block = sample_next_block(&vm, &caller_private_key, &[execution.clone()], 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(); + } + + // Upgrade the program. + let transaction = vm.deploy(&caller_private_key, &program_v1, None, 0, None, rng).unwrap(); + let block = sample_next_block(&vm, &caller_private_key, &[transaction], 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(); + + // Add the third transaction to a block. + // It is expected to be aborted because the program has been upgraded. + let block = sample_next_block(&vm, &caller_private_key, &[executions[2].clone()], rng).unwrap(); + assert_eq!(block.transactions().num_accepted(), 0); + assert_eq!(block.transactions().num_rejected(), 0); + assert_eq!(block.aborted_transaction_ids().len(), 1); + vm.add_next_block(&block).unwrap(); +} + +// This test verifies that `credits.aleo` transactions can be executed and added to blocks after the upgrade to V9. +#[test] +fn test_credits_executions() { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + let caller_address = Address::try_from(&caller_private_key).unwrap(); + + // Initialize the VM. + let vm: crate::VM = + sample_vm_at_height(CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V9).unwrap() - 1, rng); + + // Generate two executions of `transfer_public`. + let transfer_1 = vm + .execute( + &caller_private_key, + ("credits.aleo", "transfer_public"), + vec![Value::from_str(&format!("{caller_address}")).unwrap(), Value::from_str("2u64").unwrap()].into_iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + assert!(vm.check_transaction(&transfer_1, None, rng).is_ok()); + + let transfer_2 = vm + .execute( + &caller_private_key, + ("credits.aleo", "transfer_public"), + vec![Value::from_str(&format!("{caller_address}")).unwrap(), Value::from_str("4u64").unwrap()].into_iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + assert!(vm.check_transaction(&transfer_2, None, rng).is_ok()); + + // Add the first transaction to a block. + let block = sample_next_block(&vm, &caller_private_key, &[transfer_1], 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(); + + // Skip to consensus height V9. + while vm.block_store().current_block_height() <= CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V9).unwrap() { + let block = sample_next_block(&vm, &caller_private_key, &[], rng).unwrap(); + vm.add_next_block(&block).unwrap(); + } + + // Add the second transaction to a block. + let block = sample_next_block(&vm, &caller_private_key, &[transfer_2], rng).unwrap(); + assert_eq!(block.transactions().num_accepted(), 1); + assert_eq!(block.transactions().num_rejected(), 0); + assert_eq!(block.aborted_transaction_ids().len(), 0); +} + +// This tests verifies that: +// - a set of programs with cyclic imports can be deployed and executed. +// - a set of programs with cyclic calls cannot be deployed. +// - the VM can be loaded from a store at the very end. +// - the VM can be loaded directly from the latest programs. +#[test] +fn test_cyclic_imports_and_call_graphs() { + let rng = &mut TestRng::default(); + + // Initialize the storage. + let store = ConsensusStore::::open(StorageMode::new_test(None)).unwrap(); + + // Initialize the VM. + let mut vm = VM::::from(store.clone()).unwrap(); + let genesis = sample_genesis_block(rng); + vm.add_next_block(&genesis).unwrap(); + + // Get the genesis private key. + let caller_private_key = sample_genesis_private_key(rng); + + // Advance the VM to `ConsensusVersion::V9`. + advance_vm_to_height( + &mut vm, + caller_private_key, + CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V9).unwrap(), + rng, + ); + + // Define the programs with cyclic imports. + let program_a_v0 = Program::from_str( + r" +program cyclic_import_a.aleo; + +function foo: + assert.eq true true; + +constructor: + assert.eq true true; + ", + ) + .unwrap(); + + let program_b_v0 = Program::from_str( + r" +import cyclic_import_a.aleo; + +program cyclic_import_b.aleo; + +function bar: + call cyclic_import_a.aleo/foo; + +constructor: + assert.eq true true; + ", + ) + .unwrap(); + + let program_a_v1 = Program::from_str( + r" +import cyclic_import_b.aleo; + +program cyclic_import_a.aleo; + +function foo: + assert.eq true true; + +constructor: + assert.eq true true; + ", + ) + .unwrap(); + + let program_a_v2 = Program::from_str( + r" +import cyclic_import_b.aleo; + +program cyclic_import_a.aleo; + +function foo: + call cyclic_import_b.aleo/bar; + +constructor: + assert.eq true true; + ", + ) + .unwrap(); + + // Deploy the first version of program A. + let transaction = vm.deploy(&caller_private_key, &program_a_v0, None, 0, None, rng).unwrap(); + let block = sample_next_block(&vm, &caller_private_key, &[transaction], 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 first version of program B. + let transaction = vm.deploy(&caller_private_key, &program_b_v0, None, 0, None, rng).unwrap(); + let block = sample_next_block(&vm, &caller_private_key, &[transaction], 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(); + + // Execute `foo` and `bar`. + let execution_foo = vm + .execute( + &caller_private_key, + ("cyclic_import_a.aleo", "foo"), + Vec::>::new().into_iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + let execution_bar = vm + .execute( + &caller_private_key, + ("cyclic_import_b.aleo", "bar"), + Vec::>::new().into_iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + let block = sample_next_block(&vm, &caller_private_key, &[execution_foo, execution_bar], rng).unwrap(); + assert_eq!(block.transactions().num_accepted(), 2); + assert_eq!(block.transactions().num_rejected(), 0); + assert_eq!(block.aborted_transaction_ids().len(), 0); + vm.add_next_block(&block).unwrap(); + + // Upgrade program A to version 1. + let transaction = vm.deploy(&caller_private_key, &program_a_v1, None, 0, None, rng).unwrap(); + let block = sample_next_block(&vm, &caller_private_key, &[transaction], 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(); + + // Execute `foo` and `bar`. + let execution_foo = vm + .execute( + &caller_private_key, + ("cyclic_import_a.aleo", "foo"), + Vec::>::new().into_iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + let execution_bar = vm + .execute( + &caller_private_key, + ("cyclic_import_b.aleo", "bar"), + Vec::>::new().into_iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + let block = sample_next_block(&vm, &caller_private_key, &[execution_foo, execution_bar], rng).unwrap(); + assert_eq!(block.transactions().num_accepted(), 2); + assert_eq!(block.transactions().num_rejected(), 0); + assert_eq!(block.aborted_transaction_ids().len(), 0); + vm.add_next_block(&block).unwrap(); + + // Attempt to upgrade program A to version 2. + let result = vm.deploy(&caller_private_key, &program_a_v2, None, 0, None, rng); + assert!(result.is_err()); + + // Drop the VM. + drop(vm); + + // Load the VM from the store. + let vm = VM::::from(store).unwrap(); + + // Check that the latest block. + let latest_block = vm.store.block_store().current_block_height(); + assert_eq!(latest_block, CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V9).unwrap() + 5); + + // Check that the programs can be executed. + let execution_foo = vm + .execute( + &caller_private_key, + ("cyclic_import_a.aleo", "foo"), + Vec::>::new().into_iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + let execution_bar = vm + .execute( + &caller_private_key, + ("cyclic_import_b.aleo", "bar"), + Vec::>::new().into_iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + let block = sample_next_block(&vm, &caller_private_key, &[execution_foo, execution_bar], rng).unwrap(); + assert_eq!(block.transactions().num_accepted(), 2); + assert_eq!(block.transactions().num_rejected(), 0); + assert_eq!(block.aborted_transaction_ids().len(), 0); + vm.add_next_block(&block).unwrap(); + + // Drop the VM. + drop(vm); + + // Initialize a new VM. + let vm = sample_vm_at_height(CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V9).unwrap(), rng); + // Add the programs to the VM. + vm.process().write().add_programs_with_editions(&[(program_a_v1, 1), (program_b_v0, 0)]).unwrap(); + + // Check that the programs can be executed. + let execution_foo = vm + .execute( + &caller_private_key, + ("cyclic_import_a.aleo", "foo"), + Vec::>::new().into_iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + let execution_bar = vm + .execute( + &caller_private_key, + ("cyclic_import_b.aleo", "bar"), + Vec::>::new().into_iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + let block = sample_next_block(&vm, &caller_private_key, &[execution_foo, execution_bar], rng).unwrap(); + assert_eq!(block.transactions().num_accepted(), 2); + assert_eq!(block.transactions().num_rejected(), 0); + assert_eq!(block.aborted_transaction_ids().len(), 0); + vm.add_next_block(&block).unwrap(); +} + +// This test checks that a program can only be upgraded after a certain block height. +#[test] +fn test_upgrade_after_block_height() -> Result<()> { + let rng = &mut TestRng::default(); + + // Initialize a new caller. + let caller_private_key = sample_genesis_private_key(rng); + + // Initialize the VM at the V9 height. + let vm: crate::VM = + sample_vm_at_height(CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V9).unwrap(), rng); + + // Define the programs. + let program_v0 = Program::from_str( + r" +program upgradable.aleo; +function foo: +constructor: + branch.eq edition 0u16 to end; + gte block.height 20u32 into r0; + assert.eq r0 true; + position end; + ", + )?; + + let program_v1 = Program::from_str( + r" +program upgradable.aleo; +function foo: +function bar: +constructor: + branch.eq edition 0u16 to end; + gte block.height 20u32 into r0; + assert.eq r0 true; + position end; + ", + )?; + + // Deploy the first version of the program. + let transaction = vm.deploy(&caller_private_key, &program_v0, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); + assert_eq!(block.height(), 18); + 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)?; + + // Attempt to deploy the second version of the program before block height 20. + let transaction = vm.deploy(&caller_private_key, &program_v1, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + assert_eq!(block.height(), 19); + assert_eq!(block.transactions().num_accepted(), 0); + assert_eq!(block.transactions().num_rejected(), 1); + assert_eq!(block.aborted_transaction_ids().len(), 0); + vm.add_next_block(&block)?; + + // Attempt to deploy the second version of the program at block height 20. + let transaction = vm.deploy(&caller_private_key, &program_v1, None, 0, None, rng)?; + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng)?; + assert_eq!(block.height(), 20); + 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)?; + + Ok(()) +} + +// This test verifies that: +// - executions of a program before an upgrade in the same block are accepted. +// - executions of a program after an upgrade in the same block are aborted. +// - an old execution of a program after an upgrade is accepted in a subsequent block, if the program contents match. +// - an old execution of a program after an upgrade is aborted in a subsequent block, if the program contents do not match. +#[test] +fn test_upgrade_and_execute_in_same_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 V9 height. + let v9_height = CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V9).unwrap(); + let vm = crate::vm::test_helpers::sample_vm_at_height(v9_height, rng); + + // Define the original program. + let program_v0 = Program::from_str( + r" +program test_one.aleo; + +constructor: + assert.eq true true; + +mapping first: + key as field.public; + value as field.public; + +function set_first: + input r0 as field.public; + input r1 as field.public; + async set_first r0 r1 into r2; + output r2 as test_one.aleo/set_first.future; +finalize set_first: + input r0 as field.public; + input r1 as field.public; + set r0 into first[r1];", + ) + .unwrap(); + // Define the V1 program with an additional mapping. + let program_v1 = Program::from_str( + r" +program test_one.aleo; + +constructor: + assert.eq true true; + +mapping first: + key as field.public; + value as field.public; + +mapping second: + key as field.public; + value as field.public; + +function set_first: + input r0 as field.public; + input r1 as field.public; + async set_first r0 r1 into r2; + output r2 as test_one.aleo/set_first.future; +finalize set_first: + input r0 as field.public; + input r1 as field.public; + set r0 into first[r1]; + set r0 into second[r1];", + ) + .unwrap(); + + // Deploy the original program. + let deployment_v0 = vm.deploy(&caller_private_key, &program_v0, None, 0, None, rng).unwrap(); + let block = sample_next_block(&vm, &caller_private_key, &[deployment_v0], 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(); + + // Execute the program. + let transaction = vm + .execute( + &caller_private_key, + ("test_one.aleo", "set_first"), + [Value::from_str("0field").unwrap(), Value::from_str("0field").unwrap()].iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + let block = sample_next_block(&vm, &caller_private_key, &[transaction], 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(); + + // Verify that the entry exists in the mapping. + let Some(Value::Plaintext(Plaintext::Literal(Literal::Field(field), _))) = vm + .finalize_store() + .get_value_confirmed( + ProgramID::from_str("test_one.aleo").unwrap(), + Identifier::from_str("first").unwrap(), + &Plaintext::from_str("0field").unwrap(), + ) + .unwrap() + else { + panic!("Expected a valid field."); + }; + assert_eq!(field, Field::from_str("0field").unwrap()); + + // Get four executions of the original program with different values. + let executions = Vec::from_iter((1..5).map(|i| { + vm.execute( + &caller_private_key, + ("test_one.aleo", "set_first"), + [Value::from_str(&format!("{i}field")).unwrap(), Value::from_str("1field").unwrap()].iter(), + None, + 0, + None, + rng, + ) + .unwrap() + })); + + // Deploy the original program again. + let deployment_v0 = vm.deploy(&caller_private_key, &program_v0, None, 0, None, rng).unwrap(); + // Add the upgrade and execution to the VM. + let block = sample_next_block( + &vm, + &caller_private_key, + &[executions[0].clone(), deployment_v0, executions[1].clone()], + rng, + ) + .unwrap(); + assert_eq!(block.transactions().num_accepted(), 2); + assert_eq!(block.transactions().num_rejected(), 0); + assert_eq!(block.aborted_transaction_ids().len(), 1); + vm.add_next_block(&block).unwrap(); + + // Check that the program was upgraded. + let edition = + *vm.process().read().get_stack(ProgramID::from_str("test_one.aleo").unwrap()).unwrap().program_edition(); + assert_eq!(edition, 1); + + // Verify that the first execution was successful. + let Some(Value::Plaintext(Plaintext::Literal(Literal::Field(field), _))) = vm + .finalize_store() + .get_value_confirmed( + ProgramID::from_str("test_one.aleo").unwrap(), + Identifier::from_str("first").unwrap(), + &Plaintext::from_str("1field").unwrap(), + ) + .unwrap() + else { + panic!("Expected a valid field."); + }; + assert_eq!(field, Field::from_str("1field").unwrap()); + + // Now add an old execution that was made before the upgrade. + // This should pass because the program contents have not changed. + let block = sample_next_block(&vm, &caller_private_key, &[executions[2].clone()], 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(); + + // Verify that the entry exists in the first mapping. + let Some(Value::Plaintext(Plaintext::Literal(Literal::Field(field), _))) = vm + .finalize_store() + .get_value_confirmed( + ProgramID::from_str("test_one.aleo").unwrap(), + Identifier::from_str("first").unwrap(), + &Plaintext::from_str("1field").unwrap(), + ) + .unwrap() + else { + panic!("Expected a valid field."); + }; + assert_eq!(field, Field::from_str("3field").unwrap()); + + // Now deploy the new program with the additional mapping. + let deployment_v1 = vm.deploy(&caller_private_key, &program_v1, None, 0, None, rng).unwrap(); + let block = sample_next_block(&vm, &caller_private_key, &[deployment_v1], 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(); + + // Check that the program was upgraded. + let edition = + *vm.process().read().get_stack(ProgramID::from_str("test_one.aleo").unwrap()).unwrap().program_edition(); + assert_eq!(edition, 2); + + // Now add an old execution that was made before the upgrade. + // This should fail because the program contents have changed. + let block = sample_next_block(&vm, &caller_private_key, &[executions[3].clone()], rng).unwrap(); + assert_eq!(block.transactions().num_accepted(), 0); + assert_eq!(block.transactions().num_rejected(), 0); + assert_eq!(block.aborted_transaction_ids().len(), 1); + vm.add_next_block(&block).unwrap(); + + // Execute the new program with the new mapping. + let transaction = vm + .execute( + &caller_private_key, + ("test_one.aleo", "set_first"), + [Value::from_str("6field").unwrap(), Value::from_str("1field").unwrap()].iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + let block = sample_next_block(&vm, &caller_private_key, &[transaction], 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(); + + // Verify that the entry exists in the second mapping. + let Some(Value::Plaintext(Plaintext::Literal(Literal::Field(field), _))) = vm + .finalize_store() + .get_value_confirmed( + ProgramID::from_str("test_one.aleo").unwrap(), + Identifier::from_str("second").unwrap(), + &Plaintext::from_str("1field").unwrap(), + ) + .unwrap() + else { + panic!("Expected a valid field."); + }; + assert_eq!(field, Field::from_str("6field").unwrap()); +} + +// This test verifies that a program can be upgraded and then upgraded again in the same block. +// Note: It is important that this invariant holds, otherwise block rollbacks in the DB can be inconsistent. +#[test] +fn test_upgrade_and_upgrade_in_same_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 V9 height. + let v9_height = CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V9).unwrap(); + let vm = crate::vm::test_helpers::sample_vm_at_height(v9_height, rng); + + // Deploy the upgradable program. + let program_v0 = Program::from_str( + r" +program test_program.aleo; +constructor: + assert.eq true true; +function foo: + ", + ) + .unwrap(); + + let deployment_v0 = vm.deploy(&caller_private_key, &program_v0, None, 0, None, rng).unwrap(); + let block = sample_next_block(&vm, &caller_private_key, &[deployment_v0], 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(); + + // Fund two accounts to pay for the two upgrades. + // This has to be done because only one deployment can be made per fee-paying address per block. + let private_key_1 = PrivateKey::new(rng).unwrap(); + let private_key_2 = PrivateKey::new(rng).unwrap(); + let address_1 = Address::try_from(&private_key_1).unwrap(); + let address_2 = Address::try_from(&private_key_2).unwrap(); + + let tx_1 = vm + .execute( + &caller_private_key, + ("credits.aleo", "transfer_public"), + [Value::from_str(&format!("{address_1}")).unwrap(), Value::from_str("100_000_000u64").unwrap()].iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + let tx_2 = vm + .execute( + &caller_private_key, + ("credits.aleo", "transfer_public"), + [Value::from_str(&format!("{address_2}")).unwrap(), Value::from_str("100_000_000u64").unwrap()].iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + + let block = sample_next_block(&vm, &caller_private_key, &[tx_1, tx_2], rng).unwrap(); + assert_eq!(block.transactions().num_accepted(), 2); + assert_eq!(block.transactions().num_rejected(), 0); + assert_eq!(block.aborted_transaction_ids().len(), 0); + vm.add_next_block(&block).unwrap(); + + // Upgrade the program twice. + let program_v1 = Program::from_str( + r" +program test_program.aleo; +constructor: + assert.eq true true; +function foo: +function bar: + ", + ) + .unwrap(); + + let program_v2 = Program::from_str( + r" +program test_program.aleo; +constructor: + assert.eq true true; +function foo: +function bar: +function baz: + ", + ) + .unwrap(); + + // Generate the deployments. + // Note that we are attempting to upgrade twice with consecutive editions. + let mut process = Process::load().unwrap(); + process.add_program(&program_v0).unwrap(); + let mut deployment_v1 = process.deploy::(&program_v1, rng).unwrap(); + deployment_v1.set_program_checksum_raw(Some(deployment_v1.program().to_checksum())); + deployment_v1.set_program_owner_raw(Some(Address::try_from(&private_key_1).unwrap())); + assert_eq!(deployment_v1.edition(), 1); + process.add_program(&program_v1).unwrap(); + let mut deployment_v2 = process.deploy::(&program_v2, rng).unwrap(); + deployment_v2.set_program_checksum_raw(Some(deployment_v2.program().to_checksum())); + deployment_v2.set_program_owner_raw(Some(Address::try_from(&private_key_2).unwrap())); + assert_eq!(deployment_v2.edition(), 2); + + // Construct the transactions. + let fee_1 = vm + .execute_fee_authorization( + process + .authorize_fee_public::( + &private_key_1, + 10_000_000, + 0, + deployment_v1.to_deployment_id().unwrap(), + rng, + ) + .unwrap(), + None, + rng, + ) + .unwrap(); + let owner_1 = ProgramOwner::new(&private_key_1, deployment_v1.to_deployment_id().unwrap(), rng).unwrap(); + let transaction_1 = Transaction::from_deployment(owner_1, deployment_v1, fee_1).unwrap(); + let fee_2 = vm + .execute_fee_authorization( + process + .authorize_fee_public::( + &private_key_2, + 10_000_000, + 0, + deployment_v2.to_deployment_id().unwrap(), + rng, + ) + .unwrap(), + None, + rng, + ) + .unwrap(); + let owner_2 = ProgramOwner::new(&private_key_2, deployment_v2.to_deployment_id().unwrap(), rng).unwrap(); + let transaction_2 = Transaction::from_deployment(owner_2, deployment_v2, fee_2).unwrap(); + + // Attempt to upgrade the program twice. + let block = sample_next_block(&vm, &caller_private_key, &[transaction_1, transaction_2], rng).unwrap(); + assert_eq!(block.transactions().num_accepted(), 1); + assert_eq!(block.transactions().num_rejected(), 0); + assert_eq!(block.aborted_transaction_ids().len(), 1); + vm.add_next_block(&block).unwrap(); + + // Attempt to upgrade twice using the same edition. + let program_v3 = Program::from_str( + r" +program test_program.aleo; +constructor: + assert.eq true true; +function foo: +function bar: +function baz: +function qux: + ", + ) + .unwrap(); + + let program_v4 = Program::from_str( + r" +program test_program.aleo; +constructor: + assert.eq true true; +function foo: +function bar: +function baz: +function qux: +function fly: + ", + ) + .unwrap(); + + // Generate the deployments. + let mut deployment_v3 = process.deploy::(&program_v3, rng).unwrap(); + deployment_v3.set_program_checksum_raw(Some(deployment_v3.program().to_checksum())); + deployment_v3.set_program_owner_raw(Some(Address::try_from(&private_key_1).unwrap())); + assert_eq!(deployment_v3.edition(), 2); + let mut deployment_v4 = process.deploy::(&program_v4, rng).unwrap(); + deployment_v4.set_program_checksum_raw(Some(deployment_v4.program().to_checksum())); + deployment_v4.set_program_owner_raw(Some(Address::try_from(&private_key_2).unwrap())); + assert_eq!(deployment_v4.edition(), 2); + + // Construct the transactions. + let fee_1 = vm + .execute_fee_authorization( + process + .authorize_fee_public::( + &private_key_1, + 10_000_000, + 0, + deployment_v3.to_deployment_id().unwrap(), + rng, + ) + .unwrap(), + None, + rng, + ) + .unwrap(); + let owner_1 = ProgramOwner::new(&private_key_1, deployment_v3.to_deployment_id().unwrap(), rng).unwrap(); + let transaction_1 = Transaction::from_deployment(owner_1, deployment_v3, fee_1).unwrap(); + let fee_2 = vm + .execute_fee_authorization( + process + .authorize_fee_public::( + &private_key_2, + 10_000_000, + 0, + deployment_v4.to_deployment_id().unwrap(), + rng, + ) + .unwrap(), + None, + rng, + ) + .unwrap(); + let owner_2 = ProgramOwner::new(&private_key_2, deployment_v4.to_deployment_id().unwrap(), rng).unwrap(); + let transaction_2 = Transaction::from_deployment(owner_2, deployment_v4, fee_2).unwrap(); + + // Attempt to upgrade the program twice. + let block = sample_next_block(&vm, &caller_private_key, &[transaction_1, transaction_2], rng).unwrap(); + assert_eq!(block.transactions().num_accepted(), 1); + assert_eq!(block.transactions().num_rejected(), 1); + assert_eq!(block.aborted_transaction_ids().len(), 0); +} + +// This test checks that: +// - an upgradable program can depend on a program deployed before `V9`. +// - if the program deployed before `V9` has not done the one-time upgrade, then the upgradable program cannot be executed. +// - once the program deployed before `V9` has done the one-time upgrade, the upgradable program can be executed. +#[test] +fn test_upgradable_program_with_pre_v9_dependency() { + 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 one block before the V9 height. + let height = CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::V9).unwrap() - 1; + let vm = crate::vm::test_helpers::sample_vm_at_height(height, rng); + + // Define the pre-V9 program. + let pre_v9_program = Program::from_str( + r" +program pre_v9_program.aleo; + +function foo: + assert.eq true true; +", + ) + .unwrap(); + + // Define the upgradable program that depends on the pre-V9 program. + let upgradable_program = Program::from_str( + r" +import pre_v9_program.aleo; +program upgradable_program.aleo; + +function bar: + call pre_v9_program.aleo/foo; + +function baz: + assert.eq true true; + +constructor: + assert.eq true true; +", + ) + .unwrap(); + + // Deploy the pre-V9 program. + let deployment = vm.deploy(&caller_private_key, &pre_v9_program, None, 0, None, rng).unwrap(); + let block = sample_next_block(&vm, &caller_private_key, &[deployment], 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 upgradable program. + let deployment = vm.deploy(&caller_private_key, &upgradable_program, None, 0, None, rng).unwrap(); + let block = sample_next_block(&vm, &caller_private_key, &[deployment], 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(); + + // Attempt to execute the upgradable program. + let transaction = vm + .execute( + &caller_private_key, + ("upgradable_program.aleo", "bar"), + Vec::>::new().into_iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + let block = sample_next_block(&vm, &caller_private_key, &[transaction], rng).unwrap(); + assert_eq!(block.transactions().num_accepted(), 0); + assert_eq!(block.transactions().num_rejected(), 0); + assert_eq!(block.aborted_transaction_ids().len(), 1); + vm.add_next_block(&block).unwrap(); + + let execution = vm + .execute( + &caller_private_key, + ("upgradable_program.aleo", "baz"), + Vec::>::new().into_iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + let block = sample_next_block(&vm, &caller_private_key, &[execution], 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(); + + // Upgrade the pre-V9 program. + let deployment = vm.deploy(&caller_private_key, &pre_v9_program, None, 0, None, rng).unwrap(); + let block = sample_next_block(&vm, &caller_private_key, &[deployment], 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(); + + // Now execute the upgradable program. + let execution = vm + .execute( + &caller_private_key, + ("upgradable_program.aleo", "bar"), + Vec::>::new().into_iter(), + None, + 0, + None, + rng, + ) + .unwrap(); + let block = sample_next_block(&vm, &caller_private_key, &[execution], 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(); +} diff --git a/synthesizer/src/vm/verify.rs b/synthesizer/src/vm/verify.rs index 3a21e48536..98c5e1c4cf 100644 --- a/synthesizer/src/vm/verify.rs +++ b/synthesizer/src/vm/verify.rs @@ -137,40 +137,43 @@ impl> VM { // Get the consensus version. let consensus_version = N::CONSENSUS_VERSION(self.block_store().current_block_height())?; - // Acquire a read lock on the process. - let process = self.process.read(); + // Construct the transaction checksum. + let checksum = Data::>::Buffer(transaction.to_bytes_le()?.into()).to_checksum::()?; - // Get the program editions. - // If the transaction is an execution - // AND the consensus version is V8 or greater - // AND any of the component program editions (except for `credits.aleo`) are zero - // then fail. - if let Transaction::Execute(id, _, execution, _) = transaction { - if consensus_version >= ConsensusVersion::V8 { - for transition in execution.transitions() { - // Get the stack. - let stack = process.get_stack(transition.program_id())?; - // Get the program ID. - let program_id = *stack.program_id(); - // If the program edition is zero, then fail. - if program_id != ProgramID::from_str("credits.aleo")? && stack.program_edition().is_zero() { - drop(process); // Drop the process lock. - bail!( - "Invalid execution transaction '{id}' - the program edition for '{program_id}' cannot be zero for `ConsensusVersion::V8` or greater. Please redeploy the program." - ); - } - } + // Get the program editions from the transaction. + + let mut program_editions = Vec::with_capacity(Transaction::::MAX_TRANSITIONS); + for transition in transaction.transitions() { + // Get the stack. + let stack = self.process.read().get_stack(transition.program_id())?; + // Get the program ID. + let program_id = *stack.program_id(); + // Get the program edition. + let edition = stack.program_edition(); + + // If the consensus version is V8 or greater and any of the component programs (except for `credits.aleo`) + // - have edition 0 + // - and the program does not have a constructor. + // then fail. + if consensus_version >= ConsensusVersion::V8 + && program_id != ProgramID::from_str("credits.aleo")? + && edition.is_zero() + && !stack.program().contains_constructor() + { + bail!( + "Invalid transaction '{}' - the program edition for '{program_id}' cannot be zero for `ConsensusVersion::V8` or greater. Please redeploy the program.", + transaction.id() + ); } + // Add the program editions. + program_editions.push(edition); } - // Drop the process lock. - drop(process); - // Construct the transaction checksum. - let checksum = Data::>::Buffer(transaction.to_bytes_le()?.into()).to_checksum::()?; + // Prepare the cache key. + let cache_key = (transaction.id(), program_editions); // Check if the transaction exists in the partially-verified cache. - let is_partially_verified = - self.partially_verified_transactions.read().peek(&(transaction.id())) == Some(&checksum); + let is_partially_verified = self.partially_verified_transactions.read().peek(&cache_key) == Some(&checksum); // Verify the fee. self.check_fee(transaction, rejected_id, is_partially_verified)?; @@ -185,30 +188,75 @@ 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 `V8` or greater, then verify that: - // - the edition is zero or one - // Otherwise, verify that: - // - the deployment edition is zero - match consensus_version >= ConsensusVersion::V8 { - true => { - ensure!( - deployment.edition() <= 1, - "Invalid deployment transaction '{id}' - edition should be zero or one for `ConsensusVersion::V8` or greater" - ) - } - false => { - ensure!( - deployment.edition().is_zero(), - "Invalid deployment transaction '{id}' - edition should be zero" - ); - } + // 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 + // - the deployment edition is zero or one. + // - the program checksum is **not** present in the deployment, + // - the program owner is **not** present in the deployment + // - the program does not use constructors, `Operand::Checksum`, `Operand::Edition`, or `Operand::ProgramOwner` + // If the `CONSENSUS_VERSION` is greater than or equal to `V9`, then verify that: + // - the program checksum is present in the deployment + // - the program owner is present in the deployment + if consensus_version < ConsensusVersion::V8 { + ensure!( + deployment.edition().is_zero(), + "Invalid deployment transaction '{id}' - edition should be zero before `ConsensusVersion::V8`", + ); + } + if consensus_version < ConsensusVersion::V9 { + ensure!( + deployment.edition() <= 1, + "Invalid deployment transaction '{id}' - edition should be zero or one for before `ConsensusVersion::V9`" + ); + ensure!( + deployment.program_checksum().is_none(), + "Invalid deployment transaction '{id}' - should not contain program checksum" + ); + ensure!( + deployment.program_owner().is_none(), + "Invalid deployment transaction '{id}' - should not contain program owner" + ); + ensure!( + !deployment.program().contains_v9_syntax(), + "Invalid deployment transaction '{id}' - program uses syntax that is not allowed before `ConsensusVersion::V9`" + ); + } + if consensus_version >= ConsensusVersion::V9 { + ensure!( + deployment.program_checksum().is_some(), + "Invalid deployment transaction '{id}' - missing program checksum" + ); + ensure!( + deployment.program_owner().is_some(), + "Invalid deployment transaction '{id}' - missing program owner" + ); } + + // 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. + ensure!( + given_owner == owner.address(), + "The program owner in the deployment did not match the owner in the transaction\n('[{}]' != '[{}]')", + given_owner, + owner.address() + ); + } + // If the edition is zero, then check that: // - The program does not exist in the store or process. - // If the edition is one, then check that: + // - The program contains a constructor. + // Otherwise, check that: // - The program exists in the store and process. // - The new edition increments the old edition by 1. - // - The new program exactly matches the old program. + // - If the new program does not contain a constructor + // - the existing program does not have a constructor + // - the new program exactly matches the existing program + // - the edition is exactly one. + // - Otherwise, if the new program contains a constructor. + // - the existing program has a constructor. + // Note. Constructor validity is checked at a later point. let is_program_in_storage = self.transaction_store().contains_program_id(deployment.program_id())?; let is_program_in_process = self.contains_program(deployment.program_id()); match deployment.edition() { @@ -217,6 +265,14 @@ impl> VM { ensure!(!is_program_in_storage, "Program ID '{}' is already deployed", deployment.program_id()); // Ensure the program does not already exist in the process. ensure!(!is_program_in_process, "Program ID '{}' already exists", deployment.program_id()); + // Ensure that the program contains a constructor if the program is deployed after `ConsensusVersion::V9`. + if consensus_version >= ConsensusVersion::V9 { + // Check that the program contains a constructor. + ensure!( + deployment.program().contains_constructor(), + "Invalid deployment transaction '{id}' - a new program after `ConsensusVersion::V9` must contain a constructor" + ); + } } new_edition => { // Check that the program exists. @@ -231,6 +287,7 @@ impl> VM { // Get the existing program. // It should be the case that the stored program matches the process program. let stack = self.process().read().get_stack(deployment.program_id())?; + let existing_program = stack.program(); // Check that the new edition increments the old edition by 1. let old_edition = *stack.program_edition(); let expected_edition = old_edition @@ -240,19 +297,42 @@ impl> VM { expected_edition == new_edition, "Invalid deployment transaction '{id}' - next edition ('{new_edition}') does not match the expected edition ('{expected_edition}')", ); - // Ensure the new program matches the old program. - ensure!( - stack.program() == deployment.program(), - "Invalid deployment transaction '{id}' - new program does not match the old program" - ); + + // Validate the deployment depending on whether the program has a constructor. + // The exact rules are listed above. + match deployment.program().contains_constructor() { + false => { + // Check that the existing program does not have a constructor. + ensure!( + !existing_program.contains_constructor(), + "Invalid deployment transaction '{id}' - the existing program has a constructor, but the deployment program does not" + ); + // Ensure the new program matches the old program. + ensure!( + existing_program == deployment.program(), + "Invalid deployment transaction '{id}' - new program does not match the old program" + ); + // Ensure that the new edition is exactly one. + ensure!( + new_edition == 1, + "Invalid deployment transaction '{id}' - programs without constructors can only be re-deployed one time." + ); + } + true => { + // Ensure the existing program has a constructor. + ensure!( + existing_program.contains_constructor(), + "Invalid deployment transaction '{id}' - the existing program does not have a constructor, but the deployment program does" + ); + } + } } } // Enforce the syntax restrictions on the programs based on the current consensus version. - // Note: We do not enforce this restriction for programs with edition one, since they may have been deployed before the restrictions were introduced. + // Note: We do not enforce this restriction for programs with non-zero editions without constructors, since they may have been deployed before the restrictions were introduced. // However, we do enforce that programs with edition one, EXACTLY match their previous edition. - // TODO(@d0cd) This logic will need to be updated to handle programs with constructors, once upgradability rolls in. - if deployment.edition() == 0 { + if deployment.edition() == 0 || deployment.program().contains_constructor() { // Check restricted keywords for the consensus version. deployment.program().check_restricted_keywords_for_consensus_version(consensus_version)?; // Perform additional program checks if the consensus version is V7 or beyond. @@ -290,7 +370,7 @@ impl> VM { // If the above checks have passed and this is not a fee transaction, // then add the transaction ID to the partially-verified transactions cache. if !matches!(transaction, Transaction::Fee(..)) && !is_partially_verified { - self.partially_verified_transactions.write().push(transaction.id(), checksum); + self.partially_verified_transactions.write().push(cache_key, checksum); } finish!(timer, "Verify the transaction"); @@ -310,7 +390,7 @@ impl> VM { // Ensure the rejected ID is not present. ensure!(rejected_id.is_none(), "Transaction '{id}' should not have a rejected ID (deployment)"); // Compute the minimum deployment cost. - let (cost, _) = deployment_cost(deployment)?; + let (cost, _) = deployment_cost(&self.process().read(), deployment)?; // Ensure the fee is sufficient to cover the cost. if *fee.base_amount()? < cost { bail!("Transaction '{id}' has an insufficient base fee (deployment) - requires {cost} microcredits") @@ -560,7 +640,7 @@ impl> VM { mod tests { use super::*; - use crate::vm::test_helpers::sample_finalize_state; + use crate::vm::test_helpers::{LedgerType, sample_finalize_state}; use console::{ account::{Address, ViewKey}, types::Field, @@ -569,6 +649,23 @@ mod tests { type CurrentNetwork = test_helpers::CurrentNetwork; + // A helper function to create the cache key for a transaction in the partially-verified transactions cache. + fn create_cache_key( + vm: &VM, + transaction: &Transaction, + ) -> (::TransactionID, Vec>) { + // Get the program editions. + let program_editions = transaction + .transitions() + .map(|transition| { + vm.process().read().get_stack(transition.program_id()).map(|stack| stack.program_edition()) + }) + .collect::>>() + .unwrap(); + // Return the cache key. + (transaction.id(), program_editions) + } + #[test] fn test_verify() { let rng = &mut TestRng::default(); @@ -576,24 +673,27 @@ mod tests { // Fetch a deployment transaction. let deployment_transaction = crate::vm::test_helpers::sample_deployment_transaction(rng); + let cache_key = create_cache_key(&vm, &deployment_transaction); // Ensure the transaction verifies. vm.check_transaction(&deployment_transaction, None, rng).unwrap(); // Ensure the partially_verified_transactions cache is updated. - assert!(vm.partially_verified_transactions.read().peek(&deployment_transaction.id()).is_some()); + assert!(vm.partially_verified_transactions.read().peek(&cache_key).is_some()); // Fetch an execution transaction. let execution_transaction = crate::vm::test_helpers::sample_execution_transaction_with_private_fee(rng); + let cache_key = create_cache_key(&vm, &execution_transaction); // Ensure the transaction verifies. vm.check_transaction(&execution_transaction, None, rng).unwrap(); // Ensure the partially_verified_transactions cache is updated. - assert!(vm.partially_verified_transactions.read().peek(&execution_transaction.id()).is_some()); + assert!(vm.partially_verified_transactions.read().peek(&cache_key).is_some()); // Fetch an execution transaction. let execution_transaction = crate::vm::test_helpers::sample_execution_transaction_with_public_fee(rng); + let cache_key = create_cache_key(&vm, &execution_transaction); // Ensure the transaction verifies. vm.check_transaction(&execution_transaction, None, rng).unwrap(); // Ensure the partially_verified_transactions cache is updated. - assert!(vm.partially_verified_transactions.read().peek(&execution_transaction.id()).is_some()); + assert!(vm.partially_verified_transactions.read().peek(&cache_key).is_some()); } #[test] @@ -607,17 +707,20 @@ mod tests { // Deploy the program. let deployment = vm.deploy_raw(&program, rng).unwrap(); + // Get the size of the cache. + let cache_size = vm.partially_verified_transactions.read().len(); + // Ensure the deployment is valid. vm.check_deployment_internal(&deployment, rng).unwrap(); - // Ensure the partially_verified_transactions cache is not updated. - assert!(vm.partially_verified_transactions.read().peek(&deployment.to_deployment_id().unwrap()).is_none()); + // Ensure the partially_verified_transactions cache has the same size. + assert_eq!(vm.partially_verified_transactions.read().len(), cache_size); // Ensure that deserialization doesn't break the transaction verification. let serialized_deployment = deployment.to_string(); let deployment_transaction: Deployment = serde_json::from_str(&serialized_deployment).unwrap(); vm.check_deployment_internal(&deployment_transaction, rng).unwrap(); - // Ensure the partially_verified_transactions cache is not updated. - assert!(vm.partially_verified_transactions.read().peek(&deployment.to_deployment_id().unwrap()).is_none()); + // Ensure the partially_verified_transactions cache has the same size. + assert_eq!(vm.partially_verified_transactions.read().len(), cache_size); } #[test] @@ -631,6 +734,9 @@ mod tests { crate::vm::test_helpers::sample_execution_transaction_with_public_fee(rng), ]; + // Get the cache size. + let cache_size = vm.partially_verified_transactions.read().len(); + for transaction in transactions { match transaction { Transaction::Execute(_, _, execution, _) => { @@ -638,23 +744,16 @@ mod tests { assert!(execution.proof().is_some()); // Verify the execution. vm.check_execution_internal(&execution, false).unwrap(); - // Ensure the partially_verified_transactions cache is not updated. - assert!( - vm.partially_verified_transactions.read().peek(&execution.to_execution_id().unwrap()).is_none() - ); + // Ensure the partially_verified_transactions cache has the same size. + assert_eq!(vm.partially_verified_transactions.read().len(), cache_size); // Ensure that deserialization doesn't break the transaction verification. let serialized_execution = execution.to_string(); let recovered_execution: Execution = serde_json::from_str(&serialized_execution).unwrap(); vm.check_execution_internal(&recovered_execution, false).unwrap(); - // Ensure the partially_verified_transactions cache is not updated. - assert!( - vm.partially_verified_transactions - .read() - .peek(&recovered_execution.to_execution_id().unwrap()) - .is_none() - ); + // Ensure the partially_verified_transactions cache has the same size. + assert_eq!(vm.partially_verified_transactions.read().len(), cache_size); } _ => panic!("Expected an execution transaction"), } @@ -672,6 +771,9 @@ mod tests { crate::vm::test_helpers::sample_execution_transaction_with_public_fee(rng), ]; + // Get the cache size. + let cache_size = vm.partially_verified_transactions.read().len(); + for transaction in transactions { match transaction { Transaction::Execute(_, _, execution, Some(fee)) => { @@ -681,15 +783,15 @@ mod tests { assert!(fee.proof().is_some()); // Verify the fee. vm.check_fee_internal(&fee, execution_id, false).unwrap(); - // Ensure the partially_verified_transactions cache is not updated. - assert!(vm.partially_verified_transactions.read().peek(&execution_id).is_none()); + // Ensure the partially_verified_transactions cache has the same size. + assert_eq!(vm.partially_verified_transactions.read().len(), cache_size); // Ensure that deserialization doesn't break the transaction verification. let serialized_fee = fee.to_string(); let recovered_fee: Fee = serde_json::from_str(&serialized_fee).unwrap(); vm.check_fee_internal(&recovered_fee, execution_id, false).unwrap(); - // Ensure the partially_verified_transactions cache is not updated. - assert!(vm.partially_verified_transactions.read().peek(&execution_id).is_none()); + // Ensure the partially_verified_transactions cache has the same size. + assert_eq!(vm.partially_verified_transactions.read().len(), cache_size); } _ => panic!("Expected an execution with a fee"), } @@ -709,21 +811,24 @@ mod tests { // Fetch a valid execution transaction with a private fee. let valid_transaction = crate::vm::test_helpers::sample_execution_transaction_with_private_fee(rng); + let cache_key = create_cache_key(&vm, &valid_transaction); vm.check_transaction(&valid_transaction, None, rng).unwrap(); // Ensure the partially_verified_transactions cache is updated. - assert!(vm.partially_verified_transactions.read().peek(&valid_transaction.id()).is_some()); + assert!(vm.partially_verified_transactions.read().peek(&cache_key).is_some()); // Fetch a valid execution transaction with a public fee. let valid_transaction = crate::vm::test_helpers::sample_execution_transaction_with_public_fee(rng); + let cache_key = create_cache_key(&vm, &valid_transaction); vm.check_transaction(&valid_transaction, None, rng).unwrap(); // Ensure the partially_verified_transactions cache is updated. - assert!(vm.partially_verified_transactions.read().peek(&valid_transaction.id()).is_some()); + assert!(vm.partially_verified_transactions.read().peek(&cache_key).is_some()); // Fetch a valid execution transaction with no fee. let valid_transaction = crate::vm::test_helpers::sample_execution_transaction_without_fee(rng); + let cache_key = create_cache_key(&vm, &valid_transaction); vm.check_transaction(&valid_transaction, None, rng).unwrap(); // Ensure the partially_verified_transactions cache is updated. - assert!(vm.partially_verified_transactions.read().peek(&valid_transaction.id()).is_some()); + assert!(vm.partially_verified_transactions.read().peek(&cache_key).is_some()); } #[test] @@ -828,11 +933,12 @@ mod tests { // Execute. let transaction = vm.execute(&caller_private_key, ("testing.aleo", "initialize"), inputs, credits, 10, None, rng).unwrap(); + let cache_key = create_cache_key(&vm, &transaction); // Verify. vm.check_transaction(&transaction, None, rng).unwrap(); // Ensure the partially_verified_transactions cache is updated. - assert!(vm.partially_verified_transactions.read().peek(&transaction.id()).is_some()); + assert!(vm.partially_verified_transactions.read().peek(&cache_key).is_some()); } #[test] @@ -882,8 +988,9 @@ function compute: // Fetch a valid execution transaction with a public fee. let valid_transaction = crate::vm::test_helpers::sample_execution_transaction_with_public_fee(rng); vm.check_transaction(&valid_transaction, None, rng).unwrap(); + let cache_key = create_cache_key(&vm, &valid_transaction); // Ensure the partially_verified_transactions cache is updated. - assert!(vm.partially_verified_transactions.read().peek(&valid_transaction.id()).is_some()); + assert!(vm.partially_verified_transactions.read().peek(&cache_key).is_some()); // Mutate the execution transaction by inserting a Field::Zero as an output. let execution = valid_transaction.execution().unwrap(); @@ -932,11 +1039,12 @@ function compute: // Construct the transaction. let mutated_transaction = Transaction::from_execution(mutated_execution, Some(fee)).unwrap(); + let cache_key = create_cache_key(&vm, &mutated_transaction); // Ensure that the mutated transaction fails verification due to an extra output. assert!(vm.check_transaction(&mutated_transaction, None, rng).is_err()); // Ensure the partially_verified_transactions cache is not updated. - assert!(vm.partially_verified_transactions.read().peek(&mutated_transaction.id()).is_none()); + assert!(vm.partially_verified_transactions.read().peek(&cache_key).is_none()); } #[cfg(feature = "test")] @@ -956,6 +1064,7 @@ function compute: // Create the base transaction let transaction = crate::vm::test_helpers::sample_execution_transaction_with_public_fee(rng); + let cache_key = create_cache_key(&vm, &transaction); // Try to submit a tx with the new fee before the migration block height let fee_too_low_transaction = crate::vm::test_helpers::create_new_transaction_with_different_fee( @@ -965,7 +1074,7 @@ function compute: ); assert!(vm.check_transaction(&fee_too_low_transaction, None, rng).is_err()); // Ensure the partially_verified_transactions cache is not updated. - assert!(vm.partially_verified_transactions.read().peek(&fee_too_low_transaction.id()).is_none()); + assert!(vm.partially_verified_transactions.read().peek(&cache_key).is_none()); // Try to submit a tx with the old fee before the migration block height let old_valid_transaction = crate::vm::test_helpers::create_new_transaction_with_different_fee( @@ -973,9 +1082,10 @@ function compute: transaction.clone(), old_minimum_credits_transfer_public_fee, ); + let cache_key = create_cache_key(&vm, &old_valid_transaction); assert!(vm.check_transaction(&old_valid_transaction, None, rng).is_ok()); // Ensure the partially_verified_transactions cache is updated. - assert!(vm.partially_verified_transactions.read().peek(&old_valid_transaction.id()).is_some()); + assert!(vm.partially_verified_transactions.read().peek(&cache_key).is_some()); // Update the VM to the migration block height let private_key = test_helpers::sample_genesis_private_key(rng); @@ -1018,9 +1128,10 @@ function compute: transaction.clone(), old_minimum_credits_transfer_public_fee, ); + let cache_key = create_cache_key(&vm, &fee_too_high_transaction); assert!(vm.check_transaction(&fee_too_high_transaction, None, rng).is_ok()); // Ensure the partially_verified_transactions cache is updated. - assert!(vm.partially_verified_transactions.read().peek(&fee_too_high_transaction.id()).is_some()); + assert!(vm.partially_verified_transactions.read().peek(&cache_key).is_some()); // Try to submit a tx with the new fee after the migration block height let valid_transaction = crate::vm::test_helpers::create_new_transaction_with_different_fee( @@ -1028,9 +1139,10 @@ function compute: transaction.clone(), minimum_credits_transfer_public_fee, ); + let cache_key = create_cache_key(&vm, &valid_transaction); assert!(vm.check_transaction(&valid_transaction, None, rng).is_ok()); // Ensure the partially_verified_transactions cache is updated. - assert!(vm.partially_verified_transactions.read().peek(&valid_transaction.id()).is_some()); + assert!(vm.partially_verified_transactions.read().peek(&cache_key).is_some()); } #[cfg(feature = "test")] diff --git a/synthesizer/tests/expectations/vm/execute_and_finalize/arrays_in_finalize.out b/synthesizer/tests/expectations/vm/execute_and_finalize/arrays_in_finalize.out index ee5469cc3d..2d82ae6439 100644 --- a/synthesizer/tests/expectations/vm/execute_and_finalize/arrays_in_finalize.out +++ b/synthesizer/tests/expectations/vm/execute_and_finalize/arrays_in_finalize.out @@ -4,15 +4,15 @@ outputs: execute: arrays_in_finalize.aleo/test_arrays: outputs: - - '{"type":"public","id":"5118967490510389498563593408332855643361881818167099838056367495551683196086field","value":"[\n [\n true,\n false,\n true,\n false\n ]\n]"}' - - '{"type":"public","id":"161244190800350724515604266901291103671352113673202215208394633608141402826field","value":"[\n [\n false,\n true,\n false,\n true\n ]\n]"}' - - '{"type":"public","id":"732180900540213101598934684824468886231161382295639155627335564133876613960field","value":"[\n [\n false,\n false,\n false,\n false\n ]\n]"}' - - '{"type":"private","id":"3616430215374655411967163606466924889426035845343017410873880387198363014495field","value":"ciphertext1qvqrc4mrjecujgr0g5x0qm3ra4hexhgyg37yvvcmsa235vxy8zvscq9yvve3egaugcevxduuqpjfazej54lpjjtme2wflhzkwr2yzdcfpq0pmeuu5deqde6c9mqj93t7r66eytj7kpdwmqzlfyszqzph8cwq2rfmq75"}' - - '{"type":"future","id":"3184185279581827020862519880740546694254080631373935662875123969347427956025field","value":"{\n program_id: arrays_in_finalize.aleo,\n function_name: test_arrays,\n arguments: [\n [\n [\n true,\n false,\n true,\n false\n ]\n],\n [\n [\n false,\n true,\n false,\n true\n ]\n]\n ]\n}"}' + - '{"type":"public","id":"542740221275968686801342469669124764097724959878850998397230497919098355925field","value":"[\n [\n true,\n false,\n true,\n false\n ]\n]"}' + - '{"type":"public","id":"5693312308080591598728679767826096154528132968509424395595218800740940243342field","value":"[\n [\n false,\n true,\n false,\n true\n ]\n]"}' + - '{"type":"public","id":"3368119257775272893216890765645856169324252349502685704961269505781015331585field","value":"[\n [\n false,\n false,\n false,\n false\n ]\n]"}' + - '{"type":"private","id":"6495332843381545741405892949032231642715412256962781728616191975309898905132field","value":"ciphertext1qvq9cur3udej4zzqdk8997v905tknug9z7y60kcp34a3j0asz9v6kzmnvwps6f70a7e0jnpr7wqj4f2srascuw9s79nem06c87nveafeqfm24n9vpmg8522rwjy4p6n0wrfllau6ecd0avsyvpme4whr72fpz2z9kkk"}' + - '{"type":"future","id":"4347550098954990697084329914675595931685052424821242629209717485789791942730field","value":"{\n program_id: arrays_in_finalize.aleo,\n function_name: test_arrays,\n arguments: [\n [\n [\n true,\n false,\n true,\n false\n ]\n],\n [\n [\n false,\n true,\n false,\n true\n ]\n]\n ]\n}"}' speculate: the execution was accepted add_next_block: succeeded. additional: - child_outputs: credits.aleo/fee_public: outputs: - - '{"type":"future","id":"3000589134503000271678431837168879644758861415863210971558080085500903023542field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1y9t0y4lvhm43qdzlfjmfzh8985vfnx9ms368p07x5lsemet5ey8qt0ssjn,\n 7030u64\n ]\n}"}' + - '{"type":"future","id":"3038074924413472027721796506622793828316482524115651295254450823296447942666field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1y9t0y4lvhm43qdzlfjmfzh8985vfnx9ms368p07x5lsemet5ey8qt0ssjn,\n 7030u64\n ]\n}"}' diff --git a/synthesizer/tests/expectations/vm/execute_and_finalize/bad_constructor_fail.out b/synthesizer/tests/expectations/vm/execute_and_finalize/bad_constructor_fail.out new file mode 100644 index 0000000000..b95dbb5605 --- /dev/null +++ b/synthesizer/tests/expectations/vm/execute_and_finalize/bad_constructor_fail.out @@ -0,0 +1,5 @@ +errors: [] +outputs: +- execute: Program 'bad_constructor.aleo' does not exist +additional: +- {} diff --git a/synthesizer/tests/expectations/vm/execute_and_finalize/branch_with_future.out b/synthesizer/tests/expectations/vm/execute_and_finalize/branch_with_future.out index 31560960ac..2190f7ecba 100644 --- a/synthesizer/tests/expectations/vm/execute_and_finalize/branch_with_future.out +++ b/synthesizer/tests/expectations/vm/execute_and_finalize/branch_with_future.out @@ -4,84 +4,84 @@ outputs: execute: branch_with_future.aleo/bar: outputs: - - '{"type":"future","id":"4349654541257851733712484481717142261930236272682244511977958530837607168540field","value":"{\n program_id: branch_with_future.aleo,\n function_name: bar,\n arguments: [\n {\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n true,\n false\n ]\n }\n \n ]\n}"}' + - '{"type":"future","id":"7288896262197555662761828224784237732052643111617430351902245259912426045153field","value":"{\n program_id: branch_with_future.aleo,\n function_name: bar,\n arguments: [\n {\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n true,\n false\n ]\n }\n \n ]\n}"}' speculate: the execution was rejected add_next_block: succeeded. - verified: true execute: branch_with_future.aleo/baz: outputs: - - '{"type":"future","id":"7156465225651669153670567832058691425591505309444339579752354761446040771896field","value":"{\n program_id: branch_with_future.aleo,\n function_name: baz,\n arguments: [\n true,\n true,\n {\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n true,\n true\n ]\n },\n {\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n true,\n true\n ]\n }\n \n ]\n}"}' + - '{"type":"future","id":"5806477145193618952034600457237812348092143858510492661939113854850409996229field","value":"{\n program_id: branch_with_future.aleo,\n function_name: baz,\n arguments: [\n true,\n true,\n {\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n true,\n true\n ]\n },\n {\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n true,\n true\n ]\n }\n \n ]\n}"}' speculate: the execution was accepted add_next_block: succeeded. - verified: true execute: branch_with_future.aleo/baz: outputs: - - '{"type":"future","id":"7929887084917992981071528229802596484171061971577465716483026041288400867308field","value":"{\n program_id: branch_with_future.aleo,\n function_name: baz,\n arguments: [\n true,\n false,\n {\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n true,\n true\n ]\n },\n {\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n true,\n true\n ]\n }\n \n ]\n}"}' + - '{"type":"future","id":"7732375940496493246688248523070942096820081079976270457877521706188569226003field","value":"{\n program_id: branch_with_future.aleo,\n function_name: baz,\n arguments: [\n true,\n false,\n {\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n true,\n true\n ]\n },\n {\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n true,\n true\n ]\n }\n \n ]\n}"}' speculate: the execution was rejected add_next_block: succeeded. - verified: true execute: branch_with_future.aleo/baz: outputs: - - '{"type":"future","id":"4387124244669988277678707281799158084862070836307836467912218130095801417735field","value":"{\n program_id: branch_with_future.aleo,\n function_name: baz,\n arguments: [\n false,\n true,\n {\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n true,\n true\n ]\n },\n {\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n true,\n true\n ]\n }\n \n ]\n}"}' + - '{"type":"future","id":"3797341010947399028826908669134250682637889099379106115729247838918509639000field","value":"{\n program_id: branch_with_future.aleo,\n function_name: baz,\n arguments: [\n false,\n true,\n {\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n true,\n true\n ]\n },\n {\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n true,\n true\n ]\n }\n \n ]\n}"}' speculate: the execution was rejected add_next_block: succeeded. - verified: true execute: branch_with_future.aleo/baz: outputs: - - '{"type":"future","id":"6871220385727096575377167279142936053084242984561135217111606446643202359426field","value":"{\n program_id: branch_with_future.aleo,\n function_name: baz,\n arguments: [\n false,\n false,\n {\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n true,\n true\n ]\n },\n {\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n true,\n true\n ]\n }\n \n ]\n}"}' + - '{"type":"future","id":"3301904300125035229933941565784439035758619729465323436407687815452213394240field","value":"{\n program_id: branch_with_future.aleo,\n function_name: baz,\n arguments: [\n false,\n false,\n {\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n true,\n true\n ]\n },\n {\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n true,\n true\n ]\n }\n \n ]\n}"}' speculate: the execution was accepted add_next_block: succeeded. - verified: true execute: branch_with_future.aleo/qux: outputs: - - '{"type":"future","id":"7691625082533535675185248621438483571507576519714791672523805051888319306804field","value":"{\n program_id: branch_with_future.aleo,\n function_name: qux,\n arguments: [\n {\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n true,\n true\n ]\n }\n \n ]\n}"}' + - '{"type":"future","id":"4286313362376425211885180911633921390545071005683778398561203496853912947050field","value":"{\n program_id: branch_with_future.aleo,\n function_name: qux,\n arguments: [\n {\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n true,\n true\n ]\n }\n \n ]\n}"}' speculate: the execution was rejected add_next_block: succeeded. additional: - child_outputs: child.aleo/foo: outputs: - - '{"type":"future","id":"4066821839035627363019095513252425481535916632743117990746438339364644178393field","value":"{\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n true,\n false\n ]\n}"}' + - '{"type":"future","id":"5773287020931987863711575982907990779231757385847176015487407111001456625619field","value":"{\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n true,\n false\n ]\n}"}' credits.aleo/fee_public: outputs: - - '{"type":"future","id":"2613303289152679764263644796441898669120144229067099012835016944529531725939field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo19f7ldyn2txn950arxwzgmd69hc92agvaavuaghq3n86fv9sq25qsuu46xk,\n 3621u64\n ]\n}"}' + - '{"type":"future","id":"3915272661465910634572705448262195861307907807052760168390534800443126698168field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo19f7ldyn2txn950arxwzgmd69hc92agvaavuaghq3n86fv9sq25qsuu46xk,\n 3621u64\n ]\n}"}' - child_outputs: child.aleo/foo: outputs: - - '{"type":"future","id":"8417514059166651929092422652486443802370557002611740486977263066514920305943field","value":"{\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n true,\n true\n ]\n}"}' + - '{"type":"future","id":"3019211196334525216747871853398793174665544747298867841982355118657545167607field","value":"{\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n true,\n true\n ]\n}"}' credits.aleo/fee_public: outputs: - - '{"type":"future","id":"8309469345561612942268787048918070893531233914266083069729005104053512659262field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo19f7ldyn2txn950arxwzgmd69hc92agvaavuaghq3n86fv9sq25qsuu46xk,\n 7974u64\n ]\n}"}' + - '{"type":"future","id":"961659173922684045198141609218572309154644400836111331721595716784555660647field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo19f7ldyn2txn950arxwzgmd69hc92agvaavuaghq3n86fv9sq25qsuu46xk,\n 7974u64\n ]\n}"}' - child_outputs: child.aleo/foo: outputs: - - '{"type":"future","id":"3127033146363111999245027976673552128969400391661501091801672524788874222615field","value":"{\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n true,\n true\n ]\n}"}' + - '{"type":"future","id":"1798765843712687450957314068296008203814756636507420443560370663186385670929field","value":"{\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n true,\n true\n ]\n}"}' credits.aleo/fee_public: outputs: - - '{"type":"future","id":"5492394340403043727842425615973564597879930966790016003675542513247296031280field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo19f7ldyn2txn950arxwzgmd69hc92agvaavuaghq3n86fv9sq25qsuu46xk,\n 7974u64\n ]\n}"}' + - '{"type":"future","id":"1210612280686661367361253942867018761124954661745931954499318480697981568305field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo19f7ldyn2txn950arxwzgmd69hc92agvaavuaghq3n86fv9sq25qsuu46xk,\n 7974u64\n ]\n}"}' - child_outputs: child.aleo/foo: outputs: - - '{"type":"future","id":"5099648210051699618996514723249385558813313508051744395420944783022831005411field","value":"{\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n true,\n true\n ]\n}"}' + - '{"type":"future","id":"6029082698397467604081713435601004270195378865776994947121620171999227885109field","value":"{\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n true,\n true\n ]\n}"}' credits.aleo/fee_public: outputs: - - '{"type":"future","id":"3383020751149690982777642549618020904243993872390675320508884135959073648649field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo19f7ldyn2txn950arxwzgmd69hc92agvaavuaghq3n86fv9sq25qsuu46xk,\n 7974u64\n ]\n}"}' + - '{"type":"future","id":"1938364120780446434509800674665974905636761344439420590944698083000571140139field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo19f7ldyn2txn950arxwzgmd69hc92agvaavuaghq3n86fv9sq25qsuu46xk,\n 7974u64\n ]\n}"}' - child_outputs: child.aleo/foo: outputs: - - '{"type":"future","id":"2397357930305076590612023632220645805611104933817690455576802556863813348723field","value":"{\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n true,\n true\n ]\n}"}' + - '{"type":"future","id":"8064123217334542965607926683353979750928224176532218151884193071858538156788field","value":"{\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n true,\n true\n ]\n}"}' credits.aleo/fee_public: outputs: - - '{"type":"future","id":"6724859419590673150226254067399010207763687438703722064372966107352764993418field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo19f7ldyn2txn950arxwzgmd69hc92agvaavuaghq3n86fv9sq25qsuu46xk,\n 7974u64\n ]\n}"}' + - '{"type":"future","id":"7015128911096876946539909218219730178278401643473612144063012688796426003966field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo19f7ldyn2txn950arxwzgmd69hc92agvaavuaghq3n86fv9sq25qsuu46xk,\n 7974u64\n ]\n}"}' - child_outputs: child.aleo/foo: outputs: - - '{"type":"future","id":"7522901013232700814359199912611479580244030108478751003303368341151461327327field","value":"{\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n true,\n true\n ]\n}"}' + - '{"type":"future","id":"3849642499112412030803481642176681888240998647888140742750689315632344035837field","value":"{\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n true,\n true\n ]\n}"}' credits.aleo/fee_public: outputs: - - '{"type":"future","id":"4035050209265756669881165842242726167650106165529792885505471346258976939409field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo19f7ldyn2txn950arxwzgmd69hc92agvaavuaghq3n86fv9sq25qsuu46xk,\n 3521u64\n ]\n}"}' + - '{"type":"future","id":"3460972751449057203032326214906472011908645628051678143477106640381247124463field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo19f7ldyn2txn950arxwzgmd69hc92agvaavuaghq3n86fv9sq25qsuu46xk,\n 3521u64\n ]\n}"}' diff --git a/synthesizer/tests/expectations/vm/execute_and_finalize/child_and_parent.out b/synthesizer/tests/expectations/vm/execute_and_finalize/child_and_parent.out index 0328cd2ae4..b9235b7ef9 100644 --- a/synthesizer/tests/expectations/vm/execute_and_finalize/child_and_parent.out +++ b/synthesizer/tests/expectations/vm/execute_and_finalize/child_and_parent.out @@ -4,30 +4,30 @@ outputs: execute: child.aleo/foo: outputs: - - '{"type":"public","id":"990518176319329300540260363339673789976107460730294067348711019503474061851field","value":"aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx"}' - - '{"type":"public","id":"7571316186508319878827016469809649549608056961397657756993177747855651462866field","value":"aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx"}' + - '{"type":"public","id":"2640838416790640681168590379361283273826520388801373579901976371772330215645field","value":"aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx"}' + - '{"type":"public","id":"5387158298738959004626274281066907362332577788890240735651079058270864719628field","value":"aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx"}' speculate: the execution was accepted add_next_block: succeeded. - verified: true execute: parent.aleo/foo: outputs: - - '{"type":"public","id":"1884811560356148016006955635652965080079915936694811538333847482246480744363field","value":"aleo16w8t56s7v6ud7vu33fr388ph0dq0c7yhp597cyjt88rr3nultcyqcyk9yy"}' - - '{"type":"public","id":"5938049560052736143774141427220670377885179425739877702748657061561680738197field","value":"aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx"}' - - '{"type":"public","id":"6528548732759919272232987513159497491013177803981585005450673841162680315749field","value":"aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx"}' - - '{"type":"public","id":"5026832691298704787131751832692789283727293425012656525725701291723822396843field","value":"aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx"}' + - '{"type":"public","id":"6265026950630234503092058046125339209457273735590914938715183221666222246457field","value":"aleo16w8t56s7v6ud7vu33fr388ph0dq0c7yhp597cyjt88rr3nultcyqcyk9yy"}' + - '{"type":"public","id":"1429338900608789218901911489004022088135326571029940497750340832023552242594field","value":"aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx"}' + - '{"type":"public","id":"4155471736895534237641386731051276776621783378799426686993703543646477781451field","value":"aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx"}' + - '{"type":"public","id":"5560078235395125585245723397453169837709006667683755191761807699301767495524field","value":"aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx"}' speculate: the execution was accepted add_next_block: succeeded. additional: - child_outputs: credits.aleo/fee_public: outputs: - - '{"type":"future","id":"5082208301651373176623590811075106181267408732555436878022129644210755250685field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx,\n 1276u64\n ]\n}"}' + - '{"type":"future","id":"501500725828144533217226455002919340777005403436583492675058159393287504376field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx,\n 1276u64\n ]\n}"}' - child_outputs: child.aleo/foo: outputs: - - '{"type":"public","id":"6466295524147957495454604434721348918327651050212037504999787142417111103417field","value":"aleo16w8t56s7v6ud7vu33fr388ph0dq0c7yhp597cyjt88rr3nultcyqcyk9yy"}' - - '{"type":"public","id":"6332065223486256012523838059722457533475412222424700446882093528089704062322field","value":"aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx"}' + - '{"type":"public","id":"4458808719933941814357812178278675941692864173438054238415459239441992146677field","value":"aleo16w8t56s7v6ud7vu33fr388ph0dq0c7yhp597cyjt88rr3nultcyqcyk9yy"}' + - '{"type":"public","id":"5586036311455352058668224451675506955515461320374690487363833372659773226707field","value":"aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx"}' credits.aleo/fee_public: outputs: - - '{"type":"future","id":"2655408226458975792257147092852146628510817001169201237526883739767924449396field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx,\n 2187u64\n ]\n}"}' + - '{"type":"future","id":"2190974601909661866559349765538841627994436964804945134497362838590004983169field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx,\n 2187u64\n ]\n}"}' diff --git a/synthesizer/tests/expectations/vm/execute_and_finalize/complex_finalization.out b/synthesizer/tests/expectations/vm/execute_and_finalize/complex_finalization.out index 8b91293e73..3eb4ec9c83 100644 --- a/synthesizer/tests/expectations/vm/execute_and_finalize/complex_finalization.out +++ b/synthesizer/tests/expectations/vm/execute_and_finalize/complex_finalization.out @@ -4,23 +4,23 @@ outputs: execute: four_program.aleo/a: outputs: - - '{"type":"future","id":"7488035112769778531941130674288250753932875426357293074621480187752757492078field","value":"{\n program_id: four_program.aleo,\n function_name: a,\n arguments: [\n {\n program_id: two_program.aleo,\n function_name: b,\n arguments: [\n {\n program_id: zero_program.aleo,\n function_name: c,\n arguments: [\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n },\n {\n program_id: one_program.aleo,\n function_name: d,\n arguments: [\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n },\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n },\n {\n program_id: three_program.aleo,\n function_name: e,\n arguments: [\n {\n program_id: two_program.aleo,\n function_name: b,\n arguments: [\n {\n program_id: zero_program.aleo,\n function_name: c,\n arguments: [\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n },\n {\n program_id: one_program.aleo,\n function_name: d,\n arguments: [\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n },\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n },\n {\n program_id: one_program.aleo,\n function_name: d,\n arguments: [\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n },\n {\n program_id: zero_program.aleo,\n function_name: c,\n arguments: [\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n },\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n },\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n}"}' + - '{"type":"future","id":"6624544072709854553124716937842773706474052851811629404684416555510635043634field","value":"{\n program_id: four_program.aleo,\n function_name: a,\n arguments: [\n {\n program_id: two_program.aleo,\n function_name: b,\n arguments: [\n {\n program_id: zero_program.aleo,\n function_name: c,\n arguments: [\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n },\n {\n program_id: one_program.aleo,\n function_name: d,\n arguments: [\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n },\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n },\n {\n program_id: three_program.aleo,\n function_name: e,\n arguments: [\n {\n program_id: two_program.aleo,\n function_name: b,\n arguments: [\n {\n program_id: zero_program.aleo,\n function_name: c,\n arguments: [\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n },\n {\n program_id: one_program.aleo,\n function_name: d,\n arguments: [\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n },\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n },\n {\n program_id: one_program.aleo,\n function_name: d,\n arguments: [\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n },\n {\n program_id: zero_program.aleo,\n function_name: c,\n arguments: [\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n },\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n },\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n}"}' speculate: the execution was accepted add_next_block: succeeded. additional: - child_outputs: zero_program.aleo/c: outputs: - - '{"type":"future","id":"1105398187038837622454277097477167993760830533423058949111586351604589764619field","value":"{\n program_id: zero_program.aleo,\n function_name: c,\n arguments: [\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n}"}' + - '{"type":"future","id":"8406200524271961156608176756916748664897595514724621752105572355981280564264field","value":"{\n program_id: zero_program.aleo,\n function_name: c,\n arguments: [\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n}"}' one_program.aleo/d: outputs: - - '{"type":"future","id":"5975158243521788383998782344938406090018695955257264970161167563003833099252field","value":"{\n program_id: one_program.aleo,\n function_name: d,\n arguments: [\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n}"}' + - '{"type":"future","id":"2593429506280709344045728195439866633949816207026676722564670963023606627067field","value":"{\n program_id: one_program.aleo,\n function_name: d,\n arguments: [\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n}"}' two_program.aleo/b: outputs: - - '{"type":"future","id":"4161158966539517466474051485730092186623456374553536379370067400302311614265field","value":"{\n program_id: two_program.aleo,\n function_name: b,\n arguments: [\n {\n program_id: zero_program.aleo,\n function_name: c,\n arguments: [\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n },\n {\n program_id: one_program.aleo,\n function_name: d,\n arguments: [\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n },\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n}"}' + - '{"type":"future","id":"3496359808301793679128390310694733536375569678367939301875192352708773117366field","value":"{\n program_id: two_program.aleo,\n function_name: b,\n arguments: [\n {\n program_id: zero_program.aleo,\n function_name: c,\n arguments: [\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n },\n {\n program_id: one_program.aleo,\n function_name: d,\n arguments: [\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n },\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n}"}' three_program.aleo/e: outputs: - - '{"type":"future","id":"7995121966159772216096753007029662298643691618139651358420839852321099732034field","value":"{\n program_id: three_program.aleo,\n function_name: e,\n arguments: [\n {\n program_id: two_program.aleo,\n function_name: b,\n arguments: [\n {\n program_id: zero_program.aleo,\n function_name: c,\n arguments: [\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n },\n {\n program_id: one_program.aleo,\n function_name: d,\n arguments: [\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n },\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n },\n {\n program_id: one_program.aleo,\n function_name: d,\n arguments: [\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n },\n {\n program_id: zero_program.aleo,\n function_name: c,\n arguments: [\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n },\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n}"}' + - '{"type":"future","id":"875803546665689878563740814892151108038356533677598939099324262663243658925field","value":"{\n program_id: three_program.aleo,\n function_name: e,\n arguments: [\n {\n program_id: two_program.aleo,\n function_name: b,\n arguments: [\n {\n program_id: zero_program.aleo,\n function_name: c,\n arguments: [\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n },\n {\n program_id: one_program.aleo,\n function_name: d,\n arguments: [\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n },\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n },\n {\n program_id: one_program.aleo,\n function_name: d,\n arguments: [\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n },\n {\n program_id: zero_program.aleo,\n function_name: c,\n arguments: [\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n },\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8\n ]\n}"}' credits.aleo/fee_public: outputs: - - '{"type":"future","id":"2041084584137005681848473929795405676585868504104125712509549320076863499451field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8,\n 267576u64\n ]\n}"}' + - '{"type":"future","id":"8342526277933567525418256192647540605768711307814792735632970351624758306274field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8,\n 182576u64\n ]\n}"}' diff --git a/synthesizer/tests/expectations/vm/execute_and_finalize/count_usages.out b/synthesizer/tests/expectations/vm/execute_and_finalize/count_usages.out index f63740483a..4d5e1a67dd 100644 --- a/synthesizer/tests/expectations/vm/execute_and_finalize/count_usages.out +++ b/synthesizer/tests/expectations/vm/execute_and_finalize/count_usages.out @@ -4,20 +4,20 @@ outputs: execute: count_usages.aleo/add_and_subtract: outputs: - - '{"type":"private","id":"8025726332455529469861621128940817257864772349923128467234740989329902604473field","value":"ciphertext1qyqfup87pnm7e4d4tmxnwsqgttw0vh706ampljq46puzqtcmfrrjvzgd8mfq5"}' - - '{"type":"future","id":"447994789052632012998133268353748451374161019548829203650569518403555383774field","value":"{\n program_id: count_usages.aleo,\n function_name: add_and_subtract,\n arguments: [\n {\n program_id: basic_math.aleo,\n function_name: add_and_count,\n arguments: [\n aleo1mrughg5ssadd9fc2uwve7l6u24xn76kyz24zsjtzta03vgkq4vpqggl6fg\n ]\n },\n {\n program_id: basic_math.aleo,\n function_name: sub_and_count,\n arguments: [\n aleo1mrughg5ssadd9fc2uwve7l6u24xn76kyz24zsjtzta03vgkq4vpqggl6fg\n ]\n }\n \n ]\n}"}' + - '{"type":"private","id":"3023515930236131869960839025783791471880463326924898265054273782132733457428field","value":"ciphertext1qyq0dl9dzsxch4m7h2d98ltnfrjpw78yf9799ur42yu6gqfsu9ee6rc28nual"}' + - '{"type":"future","id":"1423517886816398005788904795341815145649556193470048155722602997807355612221field","value":"{\n program_id: count_usages.aleo,\n function_name: add_and_subtract,\n arguments: [\n {\n program_id: basic_math.aleo,\n function_name: add_and_count,\n arguments: [\n aleo1mrughg5ssadd9fc2uwve7l6u24xn76kyz24zsjtzta03vgkq4vpqggl6fg\n ]\n },\n {\n program_id: basic_math.aleo,\n function_name: sub_and_count,\n arguments: [\n aleo1mrughg5ssadd9fc2uwve7l6u24xn76kyz24zsjtzta03vgkq4vpqggl6fg\n ]\n }\n \n ]\n}"}' speculate: the execution was accepted add_next_block: succeeded. additional: - child_outputs: basic_math.aleo/add_and_count: outputs: - - '{"type":"private","id":"5275033890974719864618755320118789008496864993638234934817992876415596007788field","value":"ciphertext1qyqzjf0yj9kectcvg7u3dramjqfkady9n85lfsnt5t76hrufgdtd5qgzl8axw"}' - - '{"type":"future","id":"3797505847103929698534733656753772431719587131875982928485169994137133253960field","value":"{\n program_id: basic_math.aleo,\n function_name: add_and_count,\n arguments: [\n aleo1mrughg5ssadd9fc2uwve7l6u24xn76kyz24zsjtzta03vgkq4vpqggl6fg\n ]\n}"}' + - '{"type":"private","id":"6833670663356156349465561959119895264365617520846968285345871566308405139526field","value":"ciphertext1qyq0zc0paeqfzjmxzxzargqtqx6y9uxuqk4npne7vl5w6cqf8tjy6zsr3r0y3"}' + - '{"type":"future","id":"3923899012867609941959962112962879199673952367633890388822764303857066712565field","value":"{\n program_id: basic_math.aleo,\n function_name: add_and_count,\n arguments: [\n aleo1mrughg5ssadd9fc2uwve7l6u24xn76kyz24zsjtzta03vgkq4vpqggl6fg\n ]\n}"}' basic_math.aleo/sub_and_count: outputs: - - '{"type":"private","id":"5306487522628771911536425647010519062243539602607185695916137180263907877845field","value":"ciphertext1qyqtm7ftauwqqdeulvte8ju2tlh63r2zasjkn0sllu90544x4y48jzcxr46pm"}' - - '{"type":"future","id":"7178387428289602600711092668076659846217709523786758217423314545472680326773field","value":"{\n program_id: basic_math.aleo,\n function_name: sub_and_count,\n arguments: [\n aleo1mrughg5ssadd9fc2uwve7l6u24xn76kyz24zsjtzta03vgkq4vpqggl6fg\n ]\n}"}' + - '{"type":"private","id":"1460644660379633701080985990468232393875083890228867255811657931912367439589field","value":"ciphertext1qyqw8ke6g9adcajxqsj2qg5738wzquj40t8h7dlfpj5n8883pfyzzpqa88t0t"}' + - '{"type":"future","id":"7122641423905167294621281667571156080981196550145662425043615289686030457355field","value":"{\n program_id: basic_math.aleo,\n function_name: sub_and_count,\n arguments: [\n aleo1mrughg5ssadd9fc2uwve7l6u24xn76kyz24zsjtzta03vgkq4vpqggl6fg\n ]\n}"}' credits.aleo/fee_public: outputs: - - '{"type":"future","id":"4545791856085096226280028672626922044233856981347579201659401898452378055384field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8,\n 54628u64\n ]\n}"}' + - '{"type":"future","id":"4579779074442983777666172405716625108059682059885801816890825845153553791389field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1kw4knandael9qcpxs6g36rr6h7dwvjz6q25ueah6zz9v57zjlvxsx5llq8,\n 37628u64\n ]\n}"}' diff --git a/synthesizer/tests/expectations/vm/execute_and_finalize/future_out_of_order.out b/synthesizer/tests/expectations/vm/execute_and_finalize/future_out_of_order.out index 61d41427ab..9928aff884 100644 --- a/synthesizer/tests/expectations/vm/execute_and_finalize/future_out_of_order.out +++ b/synthesizer/tests/expectations/vm/execute_and_finalize/future_out_of_order.out @@ -4,17 +4,17 @@ outputs: execute: parent.aleo/foo: outputs: - - '{"type":"future","id":"8396518654947913382596128977772702228690377527176833533645318645045646582007field","value":"{\n program_id: parent.aleo,\n function_name: foo,\n arguments: [\n {\n program_id: child.aleo,\n function_name: boo,\n arguments: [\n aleo16w8t56s7v6ud7vu33fr388ph0dq0c7yhp597cyjt88rr3nultcyqcyk9yy\n ]\n },\n {\n program_id: child.aleo,\n function_name: boo,\n arguments: [\n aleo16w8t56s7v6ud7vu33fr388ph0dq0c7yhp597cyjt88rr3nultcyqcyk9yy\n ]\n },\n {\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n aleo16w8t56s7v6ud7vu33fr388ph0dq0c7yhp597cyjt88rr3nultcyqcyk9yy\n ]\n },\n {\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n aleo16w8t56s7v6ud7vu33fr388ph0dq0c7yhp597cyjt88rr3nultcyqcyk9yy\n ]\n },\n {\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n aleo16w8t56s7v6ud7vu33fr388ph0dq0c7yhp597cyjt88rr3nultcyqcyk9yy\n ]\n },\n {\n program_id: child.aleo,\n function_name: boo,\n arguments: [\n aleo16w8t56s7v6ud7vu33fr388ph0dq0c7yhp597cyjt88rr3nultcyqcyk9yy\n ]\n }\n \n ]\n}"}' + - '{"type":"future","id":"4395560034107832513323628742481552649353537273329387245065974304497269001184field","value":"{\n program_id: parent.aleo,\n function_name: foo,\n arguments: [\n {\n program_id: child.aleo,\n function_name: boo,\n arguments: [\n aleo16w8t56s7v6ud7vu33fr388ph0dq0c7yhp597cyjt88rr3nultcyqcyk9yy\n ]\n },\n {\n program_id: child.aleo,\n function_name: boo,\n arguments: [\n aleo16w8t56s7v6ud7vu33fr388ph0dq0c7yhp597cyjt88rr3nultcyqcyk9yy\n ]\n },\n {\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n aleo16w8t56s7v6ud7vu33fr388ph0dq0c7yhp597cyjt88rr3nultcyqcyk9yy\n ]\n },\n {\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n aleo16w8t56s7v6ud7vu33fr388ph0dq0c7yhp597cyjt88rr3nultcyqcyk9yy\n ]\n },\n {\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n aleo16w8t56s7v6ud7vu33fr388ph0dq0c7yhp597cyjt88rr3nultcyqcyk9yy\n ]\n },\n {\n program_id: child.aleo,\n function_name: boo,\n arguments: [\n aleo16w8t56s7v6ud7vu33fr388ph0dq0c7yhp597cyjt88rr3nultcyqcyk9yy\n ]\n }\n \n ]\n}"}' speculate: the execution was accepted add_next_block: succeeded. additional: - child_outputs: child.aleo/foo: outputs: - - '{"type":"future","id":"877105336123067775753553219525542643707763671014898474571886106370041133297field","value":"{\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n aleo16w8t56s7v6ud7vu33fr388ph0dq0c7yhp597cyjt88rr3nultcyqcyk9yy\n ]\n}"}' + - '{"type":"future","id":"7671899122268038014356473204469308092298520316078811679906033940930728557737field","value":"{\n program_id: child.aleo,\n function_name: foo,\n arguments: [\n aleo16w8t56s7v6ud7vu33fr388ph0dq0c7yhp597cyjt88rr3nultcyqcyk9yy\n ]\n}"}' child.aleo/boo: outputs: - - '{"type":"future","id":"5870530486761511277654480605096080114583328150657015783275451277711338977264field","value":"{\n program_id: child.aleo,\n function_name: boo,\n arguments: [\n aleo16w8t56s7v6ud7vu33fr388ph0dq0c7yhp597cyjt88rr3nultcyqcyk9yy\n ]\n}"}' + - '{"type":"future","id":"5806982923642172688017959057000917992502963839856001396602806545688864915382field","value":"{\n program_id: child.aleo,\n function_name: boo,\n arguments: [\n aleo16w8t56s7v6ud7vu33fr388ph0dq0c7yhp597cyjt88rr3nultcyqcyk9yy\n ]\n}"}' credits.aleo/fee_public: outputs: - - '{"type":"future","id":"6771131934212972353336263552550081926486500346715437554294859245895172354889field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx,\n 170808u64\n ]\n}"}' + - '{"type":"future","id":"8047078705186100306656835748176365767041298947580086224756588202240941628297field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx,\n 119808u64\n ]\n}"}' diff --git a/synthesizer/tests/expectations/vm/execute_and_finalize/good_constructor.out b/synthesizer/tests/expectations/vm/execute_and_finalize/good_constructor.out new file mode 100644 index 0000000000..63397c2b0e --- /dev/null +++ b/synthesizer/tests/expectations/vm/execute_and_finalize/good_constructor.out @@ -0,0 +1,14 @@ +errors: [] +outputs: +- verified: true + execute: + good_constructor.aleo/check: + outputs: + - '{"type":"future","id":"7193918481944815281366135374398693434460015709838433843365738057926267099778field","value":"{\n program_id: good_constructor.aleo,\n function_name: check,\n arguments: []\n}"}' + speculate: the execution was accepted + add_next_block: succeeded. +additional: +- child_outputs: + credits.aleo/fee_public: + outputs: + - '{"type":"future","id":"341861574175661095501351520594605156279706665066223111854585628429923358450field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx,\n 3224u64\n ]\n}"}' diff --git a/synthesizer/tests/expectations/vm/execute_and_finalize/hello.out b/synthesizer/tests/expectations/vm/execute_and_finalize/hello.out index 9ae4a9721c..d6d302a40c 100644 --- a/synthesizer/tests/expectations/vm/execute_and_finalize/hello.out +++ b/synthesizer/tests/expectations/vm/execute_and_finalize/hello.out @@ -4,46 +4,46 @@ outputs: execute: hello.aleo/hello: outputs: - - '{"type":"private","id":"5131573882884355182257903682248009545514589905872111590766989489749736641421field","value":"ciphertext1qyqxs4ww08ecfsfuzv3lvu2ylddz566evdd0kn2kq7tffa0jfvhrvzguk9nns"}' + - '{"type":"private","id":"7956368244586786834696717739774544525823270058796904078441478958189383835099field","value":"ciphertext1qyq8zd2d5tccnr37utfek4d2rzuylv0z5g25jvez9h8vdxa5k77hyrqtrhgmm"}' speculate: the execution was accepted add_next_block: succeeded. - verified: true execute: hello.aleo/hello: outputs: - - '{"type":"private","id":"6540258413886922786374562206523527552103922311417806085516123700513573545786field","value":"ciphertext1qyqvn6jqqsvl59jdxc56fvgrthy7s29uy59y0q2854ule9eaa3qryrgch9lan"}' + - '{"type":"private","id":"920860077685870189118339238626755267381831606124680164428136639186353645396field","value":"ciphertext1qyqtuqg9h8ymhmd2shwh698aqe4xucz033aekj0cpgrcxeq270xk2rsekl6cg"}' speculate: the execution was accepted add_next_block: succeeded. - verified: true execute: hello.aleo/goodbye: outputs: - - '{"type":"public","id":"6552887319744710599952238134121182296015937968688536410722019448386949169427field","value":"1u32"}' - - '{"type":"future","id":"5050616512049212243885955271314949172240054022718808683458392630651227324539field","value":"{\n program_id: hello.aleo,\n function_name: goodbye,\n arguments: [\n 1u32,\n 1u32\n ]\n}"}' + - '{"type":"public","id":"1955809651112329001701088734779266289561315034685386776064838314347285413684field","value":"1u32"}' + - '{"type":"future","id":"4455355398278415809665294808056405614279761790954020590066875113418320738567field","value":"{\n program_id: hello.aleo,\n function_name: goodbye,\n arguments: [\n 1u32,\n 1u32\n ]\n}"}' speculate: the execution was accepted add_next_block: succeeded. - verified: true execute: hello.aleo/goodbye: outputs: - - '{"type":"public","id":"6400980542699018627296357305462852503933659601582924856954999457729562650216field","value":"1u32"}' - - '{"type":"future","id":"7006417515763656381602371422606135723079202677418702102733862526204379263166field","value":"{\n program_id: hello.aleo,\n function_name: goodbye,\n arguments: [\n 0u32,\n 1u32\n ]\n}"}' + - '{"type":"public","id":"3924151869748706630684893242147369556423468923921448446424346943786516510663field","value":"1u32"}' + - '{"type":"future","id":"4954490551759229005332468255695867102560150464574785157699408031916189041239field","value":"{\n program_id: hello.aleo,\n function_name: goodbye,\n arguments: [\n 0u32,\n 1u32\n ]\n}"}' speculate: the execution was rejected add_next_block: succeeded. additional: - child_outputs: credits.aleo/fee_public: outputs: - - '{"type":"future","id":"4794226115128098661953863457300334535735190584732192888559305107927143173518field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo10knkelvnd55fsaarm25wch7p9suf2tqlgwy5k4nxwms6d262xyfqm2tccr,\n 1317u64\n ]\n}"}' + - '{"type":"future","id":"1859616768871582698460732494774856870427546991464712711584189806294878718709field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo10knkelvnd55fsaarm25wch7p9suf2tqlgwy5k4nxwms6d262xyfqm2tccr,\n 1317u64\n ]\n}"}' - child_outputs: credits.aleo/fee_public: outputs: - - '{"type":"future","id":"5133026235053133038853489406821436126957097823152662530596593777687622314058field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo10knkelvnd55fsaarm25wch7p9suf2tqlgwy5k4nxwms6d262xyfqm2tccr,\n 1317u64\n ]\n}"}' + - '{"type":"future","id":"3207561934868347507928583331349861264891324082736913246760626505984452423149field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo10knkelvnd55fsaarm25wch7p9suf2tqlgwy5k4nxwms6d262xyfqm2tccr,\n 1317u64\n ]\n}"}' - child_outputs: credits.aleo/fee_public: outputs: - - '{"type":"future","id":"8038906995585515708485738975797104585040095967461431356593208300992698109622field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo10knkelvnd55fsaarm25wch7p9suf2tqlgwy5k4nxwms6d262xyfqm2tccr,\n 2366u64\n ]\n}"}' + - '{"type":"future","id":"3479535060758176290078638377614820799945031376401161256410165412775376528674field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo10knkelvnd55fsaarm25wch7p9suf2tqlgwy5k4nxwms6d262xyfqm2tccr,\n 2366u64\n ]\n}"}' - child_outputs: credits.aleo/fee_public: outputs: - - '{"type":"future","id":"4944753227454044798048990117828783917614367206136243192189386592713292937485field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo10knkelvnd55fsaarm25wch7p9suf2tqlgwy5k4nxwms6d262xyfqm2tccr,\n 2366u64\n ]\n}"}' + - '{"type":"future","id":"2717433016267546152525274977083725979717961750701306522288519827439250786354field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo10knkelvnd55fsaarm25wch7p9suf2tqlgwy5k4nxwms6d262xyfqm2tccr,\n 2366u64\n ]\n}"}' diff --git a/synthesizer/tests/expectations/vm/execute_and_finalize/interleave_async_and_non_async.out b/synthesizer/tests/expectations/vm/execute_and_finalize/interleave_async_and_non_async.out index 370ae08718..9d361f6ee0 100644 --- a/synthesizer/tests/expectations/vm/execute_and_finalize/interleave_async_and_non_async.out +++ b/synthesizer/tests/expectations/vm/execute_and_finalize/interleave_async_and_non_async.out @@ -4,29 +4,29 @@ outputs: execute: mid.aleo/save_mid_rand_2: outputs: - - '{"type":"future","id":"8133252294099058744569698548841506104644319254713927471060413341001678088866field","value":"{\n program_id: mid.aleo,\n function_name: save_mid_rand_2,\n arguments: [\n {\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n }\n \n ]\n}"}' + - '{"type":"future","id":"66390982856222191962573387262289079123508218435695117651189091594134301481field","value":"{\n program_id: mid.aleo,\n function_name: save_mid_rand_2,\n arguments: [\n {\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n }\n \n ]\n}"}' speculate: the execution was accepted add_next_block: succeeded. - verified: true execute: outer.aleo/call_mid_3: outputs: - - '{"type":"future","id":"3943339733672692638395740341671994092158484825516042860641043685177097488932field","value":"{\n program_id: outer.aleo,\n function_name: call_mid_3,\n arguments: [\n {\n program_id: mid.aleo,\n function_name: save_mid_rand,\n arguments: [\n {\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n }\n \n ]\n },\n {\n program_id: mid.aleo,\n function_name: save_mid_rand,\n arguments: [\n {\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n }\n \n ]\n }\n \n ]\n}"}' + - '{"type":"future","id":"3347858157676737529148343790660687132066898906355309550762714354034039829837field","value":"{\n program_id: outer.aleo,\n function_name: call_mid_3,\n arguments: [\n {\n program_id: mid.aleo,\n function_name: save_mid_rand,\n arguments: [\n {\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n }\n \n ]\n },\n {\n program_id: mid.aleo,\n function_name: save_mid_rand,\n arguments: [\n {\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n }\n \n ]\n }\n \n ]\n}"}' speculate: the execution was accepted add_next_block: succeeded. - verified: true execute: outer.aleo/call_mid: outputs: - - '{"type":"future","id":"3234621292740822058818055697198799540048108651323455396840123946866280290387field","value":"{\n program_id: outer.aleo,\n function_name: call_mid,\n arguments: [\n {\n program_id: mid.aleo,\n function_name: save_mid_rand,\n arguments: [\n {\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n }\n \n ]\n }\n \n ]\n}"}' - speculate: the execution was rejected + - '{"type":"future","id":"3316410284995670902654933770744449180452940739027804224825513165672258172247field","value":"{\n program_id: outer.aleo,\n function_name: call_mid,\n arguments: [\n {\n program_id: mid.aleo,\n function_name: save_mid_rand,\n arguments: [\n {\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n }\n \n ]\n }\n \n ]\n}"}' + speculate: the execution was accepted add_next_block: succeeded. - verified: true execute: outer.aleo/call_mid_2: outputs: - - '{"type":"future","id":"6256136872031781770553816141201857256304896691884762229618319303437235049235field","value":"{\n program_id: outer.aleo,\n function_name: call_mid_2,\n arguments: [\n {\n program_id: mid.aleo,\n function_name: save_mid_rand,\n arguments: [\n {\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n }\n \n ]\n },\n {\n program_id: mid.aleo,\n function_name: save_mid_rand,\n arguments: [\n {\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n }\n \n ]\n }\n \n ]\n}"}' - speculate: the execution was rejected + - '{"type":"future","id":"6757023458186318963232697100526040935465656286734643308976707085990554519419field","value":"{\n program_id: outer.aleo,\n function_name: call_mid_2,\n arguments: [\n {\n program_id: mid.aleo,\n function_name: save_mid_rand,\n arguments: [\n {\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n }\n \n ]\n },\n {\n program_id: mid.aleo,\n function_name: save_mid_rand,\n arguments: [\n {\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n }\n \n ]\n }\n \n ]\n}"}' + speculate: the execution was accepted add_next_block: succeeded. - verified: true execute: @@ -50,14 +50,14 @@ outputs: execute: outer.aleo/call_mid: outputs: - - '{"type":"future","id":"1442432016017783473506018231162631985706673203904773748822729317313361172014field","value":"{\n program_id: outer.aleo,\n function_name: call_mid,\n arguments: [\n {\n program_id: mid.aleo,\n function_name: save_mid_rand,\n arguments: [\n {\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n }\n \n ]\n }\n \n ]\n}"}' + - '{"type":"future","id":"3942556492103982224109432510060192754309425243015619321291875348315080639151field","value":"{\n program_id: outer.aleo,\n function_name: call_mid,\n arguments: [\n {\n program_id: mid.aleo,\n function_name: save_mid_rand,\n arguments: [\n {\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n }\n \n ]\n }\n \n ]\n}"}' speculate: the execution was accepted add_next_block: succeeded. - verified: true execute: outer.aleo/call_mid_2: outputs: - - '{"type":"future","id":"5992337061564037613503594922642940602038279609368168204573599540929191074383field","value":"{\n program_id: outer.aleo,\n function_name: call_mid_2,\n arguments: [\n {\n program_id: mid.aleo,\n function_name: save_mid_rand,\n arguments: [\n {\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n }\n \n ]\n },\n {\n program_id: mid.aleo,\n function_name: save_mid_rand,\n arguments: [\n {\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n }\n \n ]\n }\n \n ]\n}"}' + - '{"type":"future","id":"8404804264407461103404998540522789254572118335530222292873780008817390355982field","value":"{\n program_id: outer.aleo,\n function_name: call_mid_2,\n arguments: [\n {\n program_id: mid.aleo,\n function_name: save_mid_rand,\n arguments: [\n {\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n }\n \n ]\n },\n {\n program_id: mid.aleo,\n function_name: save_mid_rand,\n arguments: [\n {\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n }\n \n ]\n }\n \n ]\n}"}' speculate: the execution was accepted add_next_block: succeeded. additional: @@ -66,79 +66,79 @@ additional: outputs: [] inner.aleo/save_inner_rand: outputs: - - '{"type":"future","id":"2491454043368457724827856328992492378252414844160971129371068949933787800327field","value":"{\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n}"}' + - '{"type":"future","id":"3987091141093809820962934800916752355234237857159910145893947017700806284279field","value":"{\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n}"}' credits.aleo/fee_public: outputs: - - '{"type":"future","id":"8364085965948136284430782264975904513362941894972177369414604763679081824990field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1dg8dpzx0d53ajd87nhppq79z9vrvhelhh45frsqkusndtaasgcxqqs0ay8,\n 76697u64\n ]\n}"}' + - '{"type":"future","id":"6587888489020181813301949732888387685528046612769206514617535242875844177516field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1dg8dpzx0d53ajd87nhppq79z9vrvhelhh45frsqkusndtaasgcxqqs0ay8,\n 76697u64\n ]\n}"}' - child_outputs: inner.aleo/save_inner_rand: outputs: - - '{"type":"future","id":"5529824764130005030074933219733459982343596117334741286429948259175211128460field","value":"{\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n}"}' + - '{"type":"future","id":"963470831018226890786302123418591535505350249153978635119918672319199140139field","value":"{\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n}"}' mid.aleo/save_mid_rand: outputs: - - '{"type":"future","id":"4360736171399918590300263879039970750359601853381250666126605518412297988622field","value":"{\n program_id: mid.aleo,\n function_name: save_mid_rand,\n arguments: [\n {\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n }\n \n ]\n}"}' + - '{"type":"future","id":"581077928144649862498688800844053878073820606931490502052407039228918406860field","value":"{\n program_id: mid.aleo,\n function_name: save_mid_rand,\n arguments: [\n {\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n }\n \n ]\n}"}' mid.aleo/dummy: outputs: [] credits.aleo/fee_public: outputs: - - '{"type":"future","id":"115907321802783128984566810198532384178634534374658674607472564056856197283field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1dg8dpzx0d53ajd87nhppq79z9vrvhelhh45frsqkusndtaasgcxqqs0ay8,\n 153515u64\n ]\n}"}' + - '{"type":"future","id":"6721274460550608501424316865101676464150846160039481700841421419744009574624field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1dg8dpzx0d53ajd87nhppq79z9vrvhelhh45frsqkusndtaasgcxqqs0ay8,\n 153515u64\n ]\n}"}' - child_outputs: mid.aleo/dummy: outputs: [] inner.aleo/save_inner_rand: outputs: - - '{"type":"future","id":"4614422449220197915414605795206400101815072983960277710851966783861356960406field","value":"{\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n}"}' + - '{"type":"future","id":"7968905118542649286679682277164074591950185699106991319796713039785172010092field","value":"{\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n}"}' mid.aleo/save_mid_rand: outputs: - - '{"type":"future","id":"2508191276062236001575741846287485044265199754282255664177008646882560557252field","value":"{\n program_id: mid.aleo,\n function_name: save_mid_rand,\n arguments: [\n {\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n }\n \n ]\n}"}' + - '{"type":"future","id":"1855440188685819836712812719778566137254135084348052026158937996334190518607field","value":"{\n program_id: mid.aleo,\n function_name: save_mid_rand,\n arguments: [\n {\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n }\n \n ]\n}"}' credits.aleo/fee_public: outputs: - - '{"type":"future","id":"1073608049274464266751816702008249392786653692342830056680162189620354498415field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1dg8dpzx0d53ajd87nhppq79z9vrvhelhh45frsqkusndtaasgcxqqs0ay8,\n 78340u64\n ]\n}"}' + - '{"type":"future","id":"5973963628722296517339643647130583230687246783671311000970680429240690582054field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1dg8dpzx0d53ajd87nhppq79z9vrvhelhh45frsqkusndtaasgcxqqs0ay8,\n 78340u64\n ]\n}"}' - child_outputs: inner.aleo/save_inner_rand: outputs: - - '{"type":"future","id":"5225218680008854881439125057672129573587587270307645239381393215803081422716field","value":"{\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n}"}' + - '{"type":"future","id":"4270190083305293811022563080716671483509464761274056534406768633315807008302field","value":"{\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n}"}' mid.aleo/save_mid_rand: outputs: - - '{"type":"future","id":"4178750000665019990650488851451367780305518344784410337482918142723400529474field","value":"{\n program_id: mid.aleo,\n function_name: save_mid_rand,\n arguments: [\n {\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n }\n \n ]\n}"}' + - '{"type":"future","id":"3791209917360879596187986685498200101868078183637311569327251585199511616790field","value":"{\n program_id: mid.aleo,\n function_name: save_mid_rand,\n arguments: [\n {\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n }\n \n ]\n}"}' mid.aleo/dummy: outputs: [] credits.aleo/fee_public: outputs: - - '{"type":"future","id":"4292470820134718797108426369632556448016220649849044091832458381384800389195field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1dg8dpzx0d53ajd87nhppq79z9vrvhelhh45frsqkusndtaasgcxqqs0ay8,\n 153515u64\n ]\n}"}' + - '{"type":"future","id":"1791213727932007470683389104665730190489688315127341303730590678311399437850field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1dg8dpzx0d53ajd87nhppq79z9vrvhelhh45frsqkusndtaasgcxqqs0ay8,\n 153515u64\n ]\n}"}' - child_outputs: credits.aleo/fee_public: outputs: - - '{"type":"future","id":"2175759062123847325921919658496101458036093092082217212502708037221890199855field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1dg8dpzx0d53ajd87nhppq79z9vrvhelhh45frsqkusndtaasgcxqqs0ay8,\n 1140u64\n ]\n}"}' + - '{"type":"future","id":"8155814197351977852125810105359852146309256400354355484667060057913983642175field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1dg8dpzx0d53ajd87nhppq79z9vrvhelhh45frsqkusndtaasgcxqqs0ay8,\n 1140u64\n ]\n}"}' - child_outputs: credits.aleo/fee_public: outputs: - - '{"type":"future","id":"6194763244383380375407778496857186996903616164599482246071751164326478513956field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1dg8dpzx0d53ajd87nhppq79z9vrvhelhh45frsqkusndtaasgcxqqs0ay8,\n 1140u64\n ]\n}"}' + - '{"type":"future","id":"8016601651353257857617772817031695180537609105845769363024395416922243509366field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1dg8dpzx0d53ajd87nhppq79z9vrvhelhh45frsqkusndtaasgcxqqs0ay8,\n 1140u64\n ]\n}"}' - child_outputs: credits.aleo/fee_public: outputs: - - '{"type":"future","id":"3151232768265173099533482325200135082196611776899131767046189738654148288813field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1dg8dpzx0d53ajd87nhppq79z9vrvhelhh45frsqkusndtaasgcxqqs0ay8,\n 1140u64\n ]\n}"}' + - '{"type":"future","id":"3477265313863652642748623392595595439817271596697234098063409662410038851762field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1dg8dpzx0d53ajd87nhppq79z9vrvhelhh45frsqkusndtaasgcxqqs0ay8,\n 1140u64\n ]\n}"}' - child_outputs: mid.aleo/dummy: outputs: [] inner.aleo/save_inner_rand: outputs: - - '{"type":"future","id":"3781138230531216134803918113073564019590837492666786203651760435560048390574field","value":"{\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n}"}' + - '{"type":"future","id":"1915461933183479674526309962309904513757027618762061310947083280659370016793field","value":"{\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n}"}' mid.aleo/save_mid_rand: outputs: - - '{"type":"future","id":"1854222538487907081019921151742727624945465976988156169205987233789227119336field","value":"{\n program_id: mid.aleo,\n function_name: save_mid_rand,\n arguments: [\n {\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n }\n \n ]\n}"}' + - '{"type":"future","id":"869005991377796902815461827828314939459893969448045031415737384056492248244field","value":"{\n program_id: mid.aleo,\n function_name: save_mid_rand,\n arguments: [\n {\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n }\n \n ]\n}"}' credits.aleo/fee_public: outputs: - - '{"type":"future","id":"980755046892470614607368500543837612430909182049571628417208493317193054184field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1dg8dpzx0d53ajd87nhppq79z9vrvhelhh45frsqkusndtaasgcxqqs0ay8,\n 78340u64\n ]\n}"}' + - '{"type":"future","id":"7426283926370379323988532482644313300090841563092884275341564689822458456921field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1dg8dpzx0d53ajd87nhppq79z9vrvhelhh45frsqkusndtaasgcxqqs0ay8,\n 78340u64\n ]\n}"}' - child_outputs: inner.aleo/save_inner_rand: outputs: - - '{"type":"future","id":"1830717585596798770552682302518960343184861438855342797877280317303603466513field","value":"{\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n}"}' + - '{"type":"future","id":"4952897782159565747397640788669930592605051545708199677366387059210151986307field","value":"{\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n}"}' mid.aleo/save_mid_rand: outputs: - - '{"type":"future","id":"7936932634716586129080809761782643240019726776810406151200679219592309609450field","value":"{\n program_id: mid.aleo,\n function_name: save_mid_rand,\n arguments: [\n {\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n }\n \n ]\n}"}' + - '{"type":"future","id":"3919046599955098649429374120579863895769025732893450261345505229395307106246field","value":"{\n program_id: mid.aleo,\n function_name: save_mid_rand,\n arguments: [\n {\n program_id: inner.aleo,\n function_name: save_inner_rand,\n arguments: [\n 0field\n ]\n }\n \n ]\n}"}' mid.aleo/dummy: outputs: [] credits.aleo/fee_public: outputs: - - '{"type":"future","id":"6344729803713548482408907680045723475400123463965072084769348280630820289540field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1dg8dpzx0d53ajd87nhppq79z9vrvhelhh45frsqkusndtaasgcxqqs0ay8,\n 153515u64\n ]\n}"}' + - '{"type":"future","id":"4733849908780512251771463103162759685481094857546495475091670684240535586531field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1dg8dpzx0d53ajd87nhppq79z9vrvhelhh45frsqkusndtaasgcxqqs0ay8,\n 153515u64\n ]\n}"}' diff --git a/synthesizer/tests/expectations/vm/execute_and_finalize/many_input_and_output.out b/synthesizer/tests/expectations/vm/execute_and_finalize/many_input_and_output.out index c2b7225a7d..32bde47fce 100644 --- a/synthesizer/tests/expectations/vm/execute_and_finalize/many_input_and_output.out +++ b/synthesizer/tests/expectations/vm/execute_and_finalize/many_input_and_output.out @@ -10,66 +10,66 @@ outputs: execute: gas_dos.aleo/make_trash: outputs: - - '{"type":"public","id":"4613003495305280459172342896551360642380671085549872261236450521478375822923field","value":"0u8"}' - - '{"type":"public","id":"4743973134972472397815524098463791938088484597045636063046205639313933518929field","value":"1u8"}' - - '{"type":"public","id":"555760781568939132183826755180684628980232466161467457554743035258901627869field","value":"2u8"}' - - '{"type":"public","id":"3157694875275263094900138843883455412991376860903973463649620138981676546252field","value":"3u8"}' - - '{"type":"public","id":"4762500691148246777860506295457421886000017089715781091140762580325095831542field","value":"4u8"}' - - '{"type":"public","id":"1619722509495388564896980055966430841124315339169633235607412171424218427952field","value":"5u8"}' - - '{"type":"public","id":"8354514207710319608700853875463103658360125733069755379916199418993984711704field","value":"6u8"}' - - '{"type":"public","id":"6146446795421276186773995055780858309920249415477095573801103513078202466762field","value":"7u8"}' - - '{"type":"public","id":"2318001922627984302376561180924956547473501650251936319394563635511287937792field","value":"8u8"}' - - '{"type":"public","id":"4335724698080250340144894758211764587953579002993217725228936749078063414122field","value":"9u8"}' - - '{"type":"public","id":"8087676879614137262566286145836111514718966108005879865108580502029152532529field","value":"10u8"}' - - '{"type":"public","id":"294493159771562529206823786373039528720886487276115087913605316823853612607field","value":"11u8"}' - - '{"type":"public","id":"7204985738453874970058286203045669825080258822596932900829393698406829649806field","value":"12u8"}' - - '{"type":"public","id":"830263914519050309337445423798113726576252534917054054561542101941345296949field","value":"13u8"}' - - '{"type":"public","id":"1469344004751424930670484441438499660924597114570570881074573778558606199421field","value":"14u8"}' - - '{"type":"public","id":"3951700828605478129702178277179066393576016187563440775486667382338991730643field","value":"15u8"}' + - '{"type":"public","id":"7259954806097972580797781917325267478057192066200587135574098634754819555558field","value":"0u8"}' + - '{"type":"public","id":"2034051346104627641565921165441278305924216551643768227017657063024518708957field","value":"1u8"}' + - '{"type":"public","id":"144736068457090579085568277537044894941672697080230893609308514709336908319field","value":"2u8"}' + - '{"type":"public","id":"2448749172960824796296310894233170040826855502116141918266480642736975988512field","value":"3u8"}' + - '{"type":"public","id":"6523964534981552040062342901598971316972051179625588836171793857875512714954field","value":"4u8"}' + - '{"type":"public","id":"228845641868403781435679572566977530358085470154614380262696557565569955751field","value":"5u8"}' + - '{"type":"public","id":"6190026041460200210548771710183657729188026369895807212221483673199938830677field","value":"6u8"}' + - '{"type":"public","id":"7569485990577718387406404136293658927799642100983668932539068751658822992934field","value":"7u8"}' + - '{"type":"public","id":"3425904482204283488305824376595694888171581554319818033175420891045422737668field","value":"8u8"}' + - '{"type":"public","id":"7021744194208583437872566683682675299076553204397599698349403112782166244589field","value":"9u8"}' + - '{"type":"public","id":"2475587233698119565890449248243313880572413698574263805724776210362654205478field","value":"10u8"}' + - '{"type":"public","id":"5926564113606395436095754480416803823760213229519692859927427182845849307898field","value":"11u8"}' + - '{"type":"public","id":"6916140585778738325198581855755936862546789172744034861660030225470986281915field","value":"12u8"}' + - '{"type":"public","id":"1964356007194754826032683178672024429294150928717807150478946245328389960442field","value":"13u8"}' + - '{"type":"public","id":"3706281039753359775676272331772915145625653198890856323123292541452575677916field","value":"14u8"}' + - '{"type":"public","id":"8416539836724007503852610327565355015872936352495639417115633715123641213411field","value":"15u8"}' speculate: the execution was accepted add_next_block: succeeded. - verified: true execute: gas_dos.aleo/make_trash_private: outputs: - - '{"type":"private","id":"5075362786697228215074472689942282701176231326430148288153418644541993540260field","value":"ciphertext1qyq8e9mevs4f9d59m6euas8grjuh4ng9ra32wwalxrnmaqmt7zyhkrscfm5fc"}' - - '{"type":"private","id":"5215225807132022158772545163740191062021730292490702535858154988807087753346field","value":"ciphertext1qyq2n8kh9husn8fhthnjgl8pjx6rc4g8c8tww2cae8zgjvsyuczxwpsanm54z"}' - - '{"type":"private","id":"6175485655830032812766870418391865949244585379793763982149649085962245726608field","value":"ciphertext1qyq0er0k7vk5hl2aue0sk90qz73ck8megp55r7wq5f2sc6te5e05crsghch95"}' - - '{"type":"private","id":"5292275369500734838902716815450230196630574445733214603628601920690693640207field","value":"ciphertext1qyq88qsgf25luc6e9hlaf95zd85gv7mx9dds396g3sd2j5xq7duu6rgwm52s2"}' - - '{"type":"private","id":"5381081294119826457204466226498552808197680922513323705838868647170112039605field","value":"ciphertext1qyqwagaeurk00c7am8u9h3tp8junvm87tq8pj95s5gt986plh8ezjqc4rtrmd"}' - - '{"type":"private","id":"1338461598474952534342699170627985736475769297938400754126083218144339953603field","value":"ciphertext1qyqgfdavmh84hfusth9cx0ctlx4v9pha3x09ufemkr2yk0drj5795zsxsa4lj"}' - - '{"type":"private","id":"4001260300537073559558326413857426277645481169425487516456210690061410740879field","value":"ciphertext1qyqqmuarzt3dgqrkvdrtnekzsfskq7lh44ljteez87fx2hzm6w0egqcd8dlnf"}' - - '{"type":"private","id":"5227242698626712211718588424963656563037146375530018083082654862574376070199field","value":"ciphertext1qyqtt366wyukvrej944ejpm6v8ac48lraf5ma0mpehkq0j6v5f8y2qswy7gjt"}' - - '{"type":"private","id":"7483071445708773648853126565285398442732297851577050757881107945512071186435field","value":"ciphertext1qyqgkmpfnmz3ewj6k44xtr6l5y0plar2txg0zdwhckuh9kj863gh7pck3nxvn"}' - - '{"type":"private","id":"4981561573377555124720956600158756464459035871604435708642730148102441448296field","value":"ciphertext1qyq2crnyagzpqttanxm8lseaca7rsedajjfnm8wd8ejs4cdjpwjgxyqesl8tj"}' - - '{"type":"private","id":"2218867027475576562642471060864076730679142980473955816748250394152189017698field","value":"ciphertext1qyqrcaa9t2j4366qlp032zpsxtz3zpdfsxcdfn582py42pmtawf42rgx00mtc"}' - - '{"type":"private","id":"7393332991553464721374713719439977665925434096754056861442821575301509587543field","value":"ciphertext1qyqq3z6rz487swgnucql7e5uy484uha55lqvahhe43p9unl9h82guqq9ntknu"}' - - '{"type":"private","id":"4508485010608517903938357676657986339889610151407496428609804193804937924386field","value":"ciphertext1qyq2lrkauz3xquc54vcxyyte4694xnjxty2qt0nzy7q7swv6d4jwkpga37eyg"}' - - '{"type":"private","id":"5742046849120914998776327076031156076910427012002979898907493381061401274278field","value":"ciphertext1qyqz53j22apn3j6ztuucxnscmqyf98swr77l007qcs0jmam748vauygp2meg9"}' - - '{"type":"private","id":"955655251996890605872283601090881531746554959441612709205216411773137158294field","value":"ciphertext1qyqz5jat55vkglewug0nrpuhag5kc7n686wpfsu5hax2r2kj3d9x6pcmf5vay"}' - - '{"type":"private","id":"4373428812215437035380352561693300320904292383523519816271482268910523671786field","value":"ciphertext1qyq0vfet29vt9zgcta0js2m8t55yuvtjxzrml37hn2sfjc3km84lgpslud9mn"}' + - '{"type":"private","id":"493183150282759920668863353109974416465554912787611571204817180101948642226field","value":"ciphertext1qyqqup8fqtftrf86f3s0fm28seqa2549ly65skc8gjrpz3d6rqzcyps7x0x7e"}' + - '{"type":"private","id":"3524246391969331291831849689738306792167669821688116834088143455826393727477field","value":"ciphertext1qyqzmf7rycp0ra9x6k80d5rh2f7cyaez8y9rdwwdh7sv5f8zju4zups3q7548"}' + - '{"type":"private","id":"307487372786720934725483194589591712939594659544848506124734329193236125588field","value":"ciphertext1qyqxyr2wu02xxv2cz2hnpghl9p0a6xzv4hgn4jh6afx32ts9uetpxyg20542l"}' + - '{"type":"private","id":"7008153608550414169541943221908967031709399866248753296037114338920027753384field","value":"ciphertext1qyq9nys9339tck5rhnlw60ts4d2qckrgkaqrwdelznl0ke65hyx2uqgue35d0"}' + - '{"type":"private","id":"7236228024762666777531349083872064695581246124261178564093866647298421172582field","value":"ciphertext1qyq05hup7j59zyyemjw76gkz8pm4ztvutnz5kn4x7rtftdlklzjggqcw5h59l"}' + - '{"type":"private","id":"6926631204497461602480390138555680575276510546038614598481050777776269770585field","value":"ciphertext1qyq27dq73u7vjyyehpm4wwglmlttrsg6vxfdjaalte3ugqgv4q6mqqg7ypgym"}' + - '{"type":"private","id":"8231433128140697783132514399209610081602950442355396603941750491257519840717field","value":"ciphertext1qyqpfw7mex7hrv6ymm25fvx7uutjnz8599mslp0dktax2s0fq748jys95vs7p"}' + - '{"type":"private","id":"1304376582849203278981960231761297413550636197359338873279283609369184374750field","value":"ciphertext1qyqg7vwvx4glh9mj9mznjy7eps07fz24pyksqjakmftfukqh4wvcyrsn2s6u9"}' + - '{"type":"private","id":"7742499963323948490589167782827589646718629253248743876536412988953671998231field","value":"ciphertext1qyqv9qxxlwq6unk4v2sjv4huzs9544cczj696uapdll2lnu444s3gzg79kk4c"}' + - '{"type":"private","id":"3869684623562204048905957035028247305384891434603998813734622247857860102743field","value":"ciphertext1qyqf4d0jlwhegjerhpcc0d4wj0mucapn3232978ktj9mg50t3zwdurc0pmhxk"}' + - '{"type":"private","id":"4059935613993696856694527682017663926395558809581701352568461618322639220327field","value":"ciphertext1qyqrj2ah5c8lmmvq3ea6txzpp6r2r92cfcc0recna9fx02807scuyqqf7rc9q"}' + - '{"type":"private","id":"8012867585693166278739660740287332963241321844077582861953639525113471678163field","value":"ciphertext1qyq2yl7fscsmh35c8rfgmwcd4htwhtqxa3wssxkd4p620dvwn5w5vrsqshsrh"}' + - '{"type":"private","id":"2322512784602319515899060819947953941612587326244978376065255705423608991470field","value":"ciphertext1qyq94pgamg9k2lfc3dwj3638wlgenhu5x2asum2mfnuqg5gd3fnfuqgkk6fkh"}' + - '{"type":"private","id":"6009429989804235411635377354545260468581777154381511961856033272628147926021field","value":"ciphertext1qyq2anjdyvejyfjwp83s0uec95t6tppxd95syh487cyj0uxqrkt9yrgt98pul"}' + - '{"type":"private","id":"3324956381490179650955399904975451710867365758836534831893494392843394003427field","value":"ciphertext1qyqymq7ud73ha9h6fh00adeh4hn8ycrh5qza6z54dhy8t9yvgz2jwrcvug4q0"}' + - '{"type":"private","id":"1671245830067315825792281295381373579952957373391331016788036002446288014197field","value":"ciphertext1qyq8ftf00zqwwspklutxc3pmkjm5s6v4j3grrc0r4zwflyvxhunayqcwnfyac"}' speculate: the execution was accepted add_next_block: succeeded. - verified: true execute: gas_dos.aleo/make_trash_u64: outputs: - - '{"type":"public","id":"6561159162501436151833081072814273870341639293232110695344791012569330235567field","value":"0u64"}' - - '{"type":"public","id":"7976342331909399277968579813342476842205479436517570814610848133471694590264field","value":"1u64"}' - - '{"type":"public","id":"7959564125019725319347269801297404917426966769897554182837555918999454670634field","value":"2u64"}' - - '{"type":"public","id":"3238578569461990585696007719064622713088324042084449251953551113405629735368field","value":"3u64"}' - - '{"type":"public","id":"2652351791392146183676186801776231305352586712963350968069669298772399762725field","value":"4u64"}' - - '{"type":"public","id":"2478951039021272007380220086890354432897243703808216707237374481504263920606field","value":"5u64"}' - - '{"type":"public","id":"6972212493349432022365386848181522590861575488437719975243869032659990316485field","value":"6u64"}' - - '{"type":"public","id":"770689646887905178427189789229328921942429186529411387025482086356305613402field","value":"7u64"}' - - '{"type":"public","id":"2553434581363211725353578850893508886122005522502786996883588898494362910064field","value":"8u64"}' - - '{"type":"public","id":"789035962301829680165600926334353315362445916301262938083061420271282046080field","value":"9u64"}' - - '{"type":"public","id":"2055266195117958331915570632100818483950700115783436413748712133711381881360field","value":"10u64"}' - - '{"type":"public","id":"3383469571899269330052105933946449998458706830571461636127918368823078955516field","value":"11u64"}' - - '{"type":"public","id":"6861968034072116205338864606251025191903009704131735615422207451203652008789field","value":"12u64"}' - - '{"type":"public","id":"3625546002618020464563336277084886351400397856943513367013736399595665961595field","value":"13u64"}' - - '{"type":"public","id":"6365291161772542284506285608326579244113169513684131987526845079850460097561field","value":"14u64"}' - - '{"type":"public","id":"6911075033623689961880813487820362603810774997714972050027042318795566339300field","value":"15u64"}' + - '{"type":"public","id":"5442709230770450965442832275628400337949796697105723568498687324108669745001field","value":"0u64"}' + - '{"type":"public","id":"6714149391186173644267308452146061893150127186555669127790172497726833371776field","value":"1u64"}' + - '{"type":"public","id":"5355962417836339594892249023961949205415128834774636148892888583793318324320field","value":"2u64"}' + - '{"type":"public","id":"2022925995523925217462327311559008729376731785914705510444298369240262324227field","value":"3u64"}' + - '{"type":"public","id":"3053602548913719073394966982065978160890920607583466980887993385193662048463field","value":"4u64"}' + - '{"type":"public","id":"3910629887131682088363794659350655072591495780267741312644497311452234985455field","value":"5u64"}' + - '{"type":"public","id":"4428084052194454544094978672211926038849231938468797656922716222655631112467field","value":"6u64"}' + - '{"type":"public","id":"3843677959189883624911871681945262147053862665405440953105326738536936069480field","value":"7u64"}' + - '{"type":"public","id":"2447825422378022540981799417497546499838275697500679967904727747266876593633field","value":"8u64"}' + - '{"type":"public","id":"3014731276082308873031754821788106012214682724219509159266929567894728956227field","value":"9u64"}' + - '{"type":"public","id":"1969942199683063832278723239391192832400272687079526702589198189181977420483field","value":"10u64"}' + - '{"type":"public","id":"391270064549868607365829910055801600844132656148755604546991380118225631340field","value":"11u64"}' + - '{"type":"public","id":"3107079613601519937894711871742006437982114182237557650984591648685161693430field","value":"12u64"}' + - '{"type":"public","id":"6713172327688783666274487175229132518296529295326503841163656929716233399176field","value":"13u64"}' + - '{"type":"public","id":"6042540262556370466788402009653103677305457694778203757251160997475834103997field","value":"14u64"}' + - '{"type":"public","id":"7771686381850386266602274628936762360190034477698738257974325241764892776846field","value":"15u64"}' speculate: the execution was accepted add_next_block: succeeded. - verified: true @@ -88,60 +88,60 @@ additional: - child_outputs: credits.aleo/fee_public: outputs: - - '{"type":"future","id":"30377576158265317558755379954993028591609254873982684478794593156637801471field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo10knkelvnd55fsaarm25wch7p9suf2tqlgwy5k4nxwms6d262xyfqm2tccr,\n 1153u64\n ]\n}"}' + - '{"type":"future","id":"4320851272208144037746816087336874632815186131241877823964059805872219038643field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo10knkelvnd55fsaarm25wch7p9suf2tqlgwy5k4nxwms6d262xyfqm2tccr,\n 1153u64\n ]\n}"}' - child_outputs: credits.aleo/fee_public: outputs: - - '{"type":"future","id":"7152805026835105172873716091653957285497801764420533865012961263887557911642field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo10knkelvnd55fsaarm25wch7p9suf2tqlgwy5k4nxwms6d262xyfqm2tccr,\n 2363u64\n ]\n}"}' + - '{"type":"future","id":"1836267305331731386030230015199739469835851861113658378824078906086823314876field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo10knkelvnd55fsaarm25wch7p9suf2tqlgwy5k4nxwms6d262xyfqm2tccr,\n 2363u64\n ]\n}"}' - child_outputs: credits.aleo/fee_public: outputs: - - '{"type":"future","id":"5397557371756282637972927997465824050186286008844168472103952190845537160019field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo10knkelvnd55fsaarm25wch7p9suf2tqlgwy5k4nxwms6d262xyfqm2tccr,\n 3331u64\n ]\n}"}' + - '{"type":"future","id":"7218977075007107943517910393783634994639402103279599362715975276725043406630field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo10knkelvnd55fsaarm25wch7p9suf2tqlgwy5k4nxwms6d262xyfqm2tccr,\n 3331u64\n ]\n}"}' - child_outputs: credits.aleo/fee_public: outputs: - - '{"type":"future","id":"8367501851131349939921973827792399149698467053837913250296494006847175395216field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo10knkelvnd55fsaarm25wch7p9suf2tqlgwy5k4nxwms6d262xyfqm2tccr,\n 2591u64\n ]\n}"}' + - '{"type":"future","id":"4299878469802720674306790893254260496059843334358159823075804962354065404356field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo10knkelvnd55fsaarm25wch7p9suf2tqlgwy5k4nxwms6d262xyfqm2tccr,\n 2591u64\n ]\n}"}' - child_outputs: child.aleo/inner_trash: outputs: - - '{"type":"public","id":"4258324859805218266091084729946080164724004439941647295375853291687898384011field","value":"0u8"}' - - '{"type":"public","id":"3595631725148264940723309249621738480517311836274280006312645287099329149900field","value":"1u8"}' - - '{"type":"public","id":"2208089297394694158284480136926424455853818009780901740335992064322863047854field","value":"2u8"}' - - '{"type":"public","id":"5688569742397292953325301820131013260933141797177046282596309663462217883051field","value":"3u8"}' - - '{"type":"public","id":"2589267864343727922187358819999124949209392983206592238621055394960841163796field","value":"4u8"}' - - '{"type":"public","id":"5570995534518087989637976676554735206972855495625472364363176802857733417627field","value":"5u8"}' - - '{"type":"public","id":"8196563339749078100880844141424856480535908196271709028877208224433618877988field","value":"6u8"}' - - '{"type":"public","id":"7148315770592140943513870153102249250228881430428702700808302274830524589712field","value":"7u8"}' - - '{"type":"public","id":"6663559720474154178048021670448619949127654696632516449974104667634461721117field","value":"8u8"}' - - '{"type":"public","id":"4262855828324563363534601768851246835981293807453200126654359299458266116785field","value":"9u8"}' - - '{"type":"public","id":"6521499071950259584214330036864628156304358707158918480447672091139469907706field","value":"10u8"}' - - '{"type":"public","id":"2780007533160863507824876124934740257440095453590254944663640741879602528021field","value":"11u8"}' - - '{"type":"public","id":"721098730065242758038656029883330636635690005801014175788365363405106507710field","value":"12u8"}' - - '{"type":"public","id":"1618474055793768647053046125452575920962293293427476033678666992071443340286field","value":"13u8"}' - - '{"type":"public","id":"7590795669806505477347034268411170284443113115706119538121917286692343733758field","value":"14u8"}' - - '{"type":"public","id":"1746336793593389412246115464856173762500352378534009993091610413102060730893field","value":"15u8"}' + - '{"type":"public","id":"1724243796317204508020518973281457550295441144568833545611474086180694536651field","value":"0u8"}' + - '{"type":"public","id":"4452358403160029046043350754600173823520775370390887836554500740249720913950field","value":"1u8"}' + - '{"type":"public","id":"4091662221132870060600761634605125351714234563893249417058743952890267093262field","value":"2u8"}' + - '{"type":"public","id":"2901621713378571797350944845957375563962759647786570830290782770063666134930field","value":"3u8"}' + - '{"type":"public","id":"2954962716835353506489117023357260319188221238523951617558686539739813540689field","value":"4u8"}' + - '{"type":"public","id":"3457853017547068923734373374204582552332769262830467640370275735210929997480field","value":"5u8"}' + - '{"type":"public","id":"4426118917779849649606962965455352937025962706056096655740087352458841694690field","value":"6u8"}' + - '{"type":"public","id":"2535702937871702576468137022339033837361378465585219168086929640839616932038field","value":"7u8"}' + - '{"type":"public","id":"1622626817482993061488474460690062967468407725764048962987525379228818474846field","value":"8u8"}' + - '{"type":"public","id":"4123815309182741832320384590350088542149610043484672793225362114033482265475field","value":"9u8"}' + - '{"type":"public","id":"7915668940730073619720340945300189948774660355351517090364890752851271990112field","value":"10u8"}' + - '{"type":"public","id":"8056234340585493700700518727552185475593720506808342507216882277568023130793field","value":"11u8"}' + - '{"type":"public","id":"3877034762353396142469566246522404457671140026762567894037213027465217305580field","value":"12u8"}' + - '{"type":"public","id":"518362938149507740578643281465541923201307746213459162020359351404776650048field","value":"13u8"}' + - '{"type":"public","id":"4349071839635625530453494537018916036054381743577008606601514791749335483860field","value":"14u8"}' + - '{"type":"public","id":"7963786356633385489437233385085852594575653694580419367204864725552593568867field","value":"15u8"}' credits.aleo/fee_public: outputs: - - '{"type":"future","id":"5250440250402479079384063487005279094696810892831249221778965713200207755186field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo10knkelvnd55fsaarm25wch7p9suf2tqlgwy5k4nxwms6d262xyfqm2tccr,\n 11424u64\n ]\n}"}' + - '{"type":"future","id":"488089493152777741790159923852936644224057164115540322797301897519224003684field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo10knkelvnd55fsaarm25wch7p9suf2tqlgwy5k4nxwms6d262xyfqm2tccr,\n 11424u64\n ]\n}"}' - child_outputs: child.aleo/inner_trash_private: outputs: - - '{"type":"private","id":"7104678975139771363369594082563366139468419554792208200047887347265545145473field","value":"ciphertext1qyq9csv2ts7auvzxrkgn59a6rf8rqp6x2lf3w8ylt420d4kdtjdn6ygx59j9y"}' - - '{"type":"private","id":"4926801310215014544954805559748287683167556580631648005183029762556664410062field","value":"ciphertext1qyq0x7ckpfm9u4rgk63hu7hccv0lde7qrtk90jxz4yvc3s27ya4zspsh64e4t"}' - - '{"type":"private","id":"3025158373600515167086001776793503086916479802702564323801256579855996236632field","value":"ciphertext1qyqgfpx70l4hhrpakl68z2wk2pval26vdwcjmjxxn6frkfauerdzsqq4py8pj"}' - - '{"type":"private","id":"91252220510257006296141223052114223378062152895167144867716852708928779745field","value":"ciphertext1qyq8q940d2j3zqu70f36xxe9che64zr2dxmuj75al3s0szts5ged5qcd459hc"}' - - '{"type":"private","id":"5222252760545452319316061497006736874983610477915769389758850306932549009630field","value":"ciphertext1qyqv6srxvu332xw3fua0al4wyzl49trvf7zn494j9leqvlahjpd5vrqh7jvyq"}' - - '{"type":"private","id":"6570786723987093104854522607966362693823233854131838411624481962428800920246field","value":"ciphertext1qyqv9ynxzkjmgjz66lrk3m2u9ntnfyvtl7s53l9dv3ar6y7x3nhlgpgaty4gy"}' - - '{"type":"private","id":"2683545714029027507827575346619167326043917479293098006245564541553014010650field","value":"ciphertext1qyqrnaw6d35mje37hld3wl4e8t80cch3r4hatemlzp7f5vt37wyturqcgnkqn"}' - - '{"type":"private","id":"979466994657219968516975870288881292162693708243771712684837117887100077495field","value":"ciphertext1qyqzmsqd9qpj6pwjc6955em3vedjc0s7h9gh74740kgxc5yzt6dmszqaqu62w"}' - - '{"type":"private","id":"148667955008751009932158730385746655132283758474039163840641702069085619092field","value":"ciphertext1qyqwrn34ueq83rw56avjhqe5kf7thtycr9dykavv0cag665len6z2yqpq5pty"}' - - '{"type":"private","id":"7440731129863281004684810942181870487300540297612431366993708247292317262618field","value":"ciphertext1qyqghgjccg8mjwn6mfk83gtpdejh6h5kgs8qngk6ylphuh8g02ma7rgpe9dda"}' - - '{"type":"private","id":"5901834737807982708978098599410021094561231224925091918983759660264223855752field","value":"ciphertext1qyqyz9pm6vgud68p0fda6lhzyfhrxhz369futwgyng93su8scrxp2yqjcg5ru"}' - - '{"type":"private","id":"7104894195533495611191959689141824024501967138573740980071353687538059813029field","value":"ciphertext1qyqr9sk4jws9ay4ujvlcf9xpzuhv2pnrujnv4nd467zkjv4ur66f7pgw5ppfm"}' - - '{"type":"private","id":"3048268228937191827597549490157315246711775336097603143181590343332295202352field","value":"ciphertext1qyqv58yu833x9zepyqfdq3fga79zcrpakeum5asuhn3st9aqeud0gzctx80jg"}' - - '{"type":"private","id":"4285685390670718078925042810300519660137065409265643363257317816624967274406field","value":"ciphertext1qyqfglq63hwn08zasfr82mpk550ctlx7k4d75hns5xaah4y6e36rvpgtw0rvq"}' - - '{"type":"private","id":"3025983048580884853315325717609008421056210836218565341349583681822517287251field","value":"ciphertext1qyqx8mrevae4zw38t3lpyw797hml2ff8v6u84fksax87fym54s7yxqc2zzmn8"}' - - '{"type":"private","id":"2779056585045156925102248171451904784889711185488933072280044292443284680942field","value":"ciphertext1qyqg7e5rdtam09s2zwzmg3j3ppfppnczyvrrwaezg5xtv60njx0vjpcm0pvrh"}' + - '{"type":"private","id":"4586859951647229690441783473679283827589064788598299501191873815668591526737field","value":"ciphertext1qyqzvy0hnd9lxm2rwxf3p4925tcvfqvnpn9ayqrx26du8dnu6jee6yqu4yzyj"}' + - '{"type":"private","id":"6476899272238515978803486616948259136213466091074753882519612894745981105889field","value":"ciphertext1qyqyeh0u440p0yan27qvtz5z3qmhfnmqye4tyjugyj82egwt04j0qzqf59ct9"}' + - '{"type":"private","id":"3243925356577303043584809942128137773518521475436271515877309014786818180755field","value":"ciphertext1qyqv0yd2c9r7pk8vc6t4v8fzp82ts2qam3s5zsfpyvem45svrwm6kyg68rud8"}' + - '{"type":"private","id":"1503781643546490556142681931422278361543674498785936098842638756372649971539field","value":"ciphertext1qyq9qf8uv54avs9p7taeczak5ls38xcrrlle8y3f02k2nmyj54awkpg9hdq8f"}' + - '{"type":"private","id":"938111012839874372160226093672956624986788061281251893415381305560911024590field","value":"ciphertext1qyqw7lu257tkf6sreymzs33lef6zfdtsztrdzm83nnqcn6rt5wmyxqscwwd5t"}' + - '{"type":"private","id":"5088857525221967269957029467395076276454457807105380213218874433330681794042field","value":"ciphertext1qyq8n0x4c4leqt7y5k5e9xh8c5lz5p0ztg6jsknfcy3cjtyp4zm22pg5ptmkh"}' + - '{"type":"private","id":"2085150072928090894085503334181281449760741894288379637326791160457637293881field","value":"ciphertext1qyq9tdvyk9fmg4my3e7czgyhrdsrnqqtj6kpaamv0fex84dhguvtqzqwffdur"}' + - '{"type":"private","id":"2007246873605295195832764147312045730987328405578432993821177575379740294952field","value":"ciphertext1qyq20jl08ag47uhmdkcnah75d4zulzwcrplrffytz0dtzfr987etvpqa96fe5"}' + - '{"type":"private","id":"3622606663413648633886436404940908019662965414191732790382235394146297243190field","value":"ciphertext1qyqv5h9rauxpyrakwt4hsy7sfc926yp5extznythn780lr0d2r3tvqs0xvdpd"}' + - '{"type":"private","id":"5372517528760230395828497825325878074191016206851497720082682042857510038366field","value":"ciphertext1qyqz03hvt0jj844cscwrq5qucu4d0crysjm2yj2ycmwgvspff4f8jqc6snmh0"}' + - '{"type":"private","id":"933760228528181655231360462471626324801284681657490773047843093299553889402field","value":"ciphertext1qyq832f0jr9uffzl5a8p2rm0awtrly5akzz43vajymj7xdpl79uq2zgtxkgs5"}' + - '{"type":"private","id":"1346680418098479011739113246627558833737288349734485626943432916318201461010field","value":"ciphertext1qyq9uqhg9670zu8sk29nhj58hcdp6mjmnwpr8lnezdzhq3ctfkfrwqgg0ufsk"}' + - '{"type":"private","id":"1812420501987812286404579690083301891443829350698680581555016978162836398063field","value":"ciphertext1qyqp293ucn6ptw7wmvrp2f9x793t6rw7cm8fxxmlwlcajcffswjh2zgpcx0g2"}' + - '{"type":"private","id":"2111569803196955979837661615305022703478443748620390388507890712616763701512field","value":"ciphertext1qyqxqlda5gsvqlpqd490xvxmvkelc5xk563e3f532l570ywnnhyakzcr8l0dz"}' + - '{"type":"private","id":"8322939923460972307007536878857534150310861617854601265821267004476094039049field","value":"ciphertext1qyqfxawkkp64dactrm7mvn38meh2v8tjg5gk6vfnc9ptws58fsjukqgr7jf20"}' + - '{"type":"private","id":"5637248536105576933090316792650238040320212496415041347727836591223576264844field","value":"ciphertext1qyqvrtt4m4l603swwv60chwr26lm8m8yjx3g3n6sldw0a0t64nnh7rgn0esv8"}' credits.aleo/fee_public: outputs: - - '{"type":"future","id":"4439477569395306322955911813937256917619490308008069715554457025369290347633field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo10knkelvnd55fsaarm25wch7p9suf2tqlgwy5k4nxwms6d262xyfqm2tccr,\n 26165u64\n ]\n}"}' + - '{"type":"future","id":"748047939678285691486810181665861615871252730926484027897131269422116973130field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo10knkelvnd55fsaarm25wch7p9suf2tqlgwy5k4nxwms6d262xyfqm2tccr,\n 26165u64\n ]\n}"}' diff --git a/synthesizer/tests/expectations/vm/execute_and_finalize/mapping_operations.out b/synthesizer/tests/expectations/vm/execute_and_finalize/mapping_operations.out index 26f74a152e..e58355c0b4 100644 --- a/synthesizer/tests/expectations/vm/execute_and_finalize/mapping_operations.out +++ b/synthesizer/tests/expectations/vm/execute_and_finalize/mapping_operations.out @@ -4,44 +4,44 @@ outputs: execute: mapping_operations.aleo/empty_remove: outputs: - - '{"type":"future","id":"2995172019915059564169965195925439506348172779175289540071594220373700362692field","value":"{\n program_id: mapping_operations.aleo,\n function_name: empty_remove,\n arguments: [\n 10u8\n ]\n}"}' + - '{"type":"future","id":"4888891160681006712694186987165543725797584419996853192641173005645948452359field","value":"{\n program_id: mapping_operations.aleo,\n function_name: empty_remove,\n arguments: [\n 10u8\n ]\n}"}' speculate: the execution was accepted add_next_block: succeeded. - verified: true execute: mapping_operations.aleo/insert_contains_remove: outputs: - - '{"type":"future","id":"4734089311297117835126604932112275842487641749640619553922194716113265671275field","value":"{\n program_id: mapping_operations.aleo,\n function_name: insert_contains_remove,\n arguments: [\n 0u8,\n 0u8\n ]\n}"}' + - '{"type":"future","id":"3509379686394217411049016939937611584110741710618602709024990774839122074645field","value":"{\n program_id: mapping_operations.aleo,\n function_name: insert_contains_remove,\n arguments: [\n 0u8,\n 0u8\n ]\n}"}' speculate: the execution was accepted add_next_block: succeeded. - verified: true execute: mapping_operations.aleo/insert_contains_remove: outputs: - - '{"type":"future","id":"230840103691729731055170563366218895016668781612853841661286026410302607495field","value":"{\n program_id: mapping_operations.aleo,\n function_name: insert_contains_remove,\n arguments: [\n 0u8,\n 0u8\n ]\n}"}' + - '{"type":"future","id":"6592622081356703155348024369990101588116893255364463731534140094906062328255field","value":"{\n program_id: mapping_operations.aleo,\n function_name: insert_contains_remove,\n arguments: [\n 0u8,\n 0u8\n ]\n}"}' speculate: the execution was accepted add_next_block: succeeded. - verified: true execute: mapping_operations.aleo/insert_contains_remove: outputs: - - '{"type":"future","id":"3315769874935654804431942155043603401753352960520513759478780430664575888438field","value":"{\n program_id: mapping_operations.aleo,\n function_name: insert_contains_remove,\n arguments: [\n 0u8,\n 1u8\n ]\n}"}' + - '{"type":"future","id":"6900466920535108624517003948615989749962594076442524285603404739391174240657field","value":"{\n program_id: mapping_operations.aleo,\n function_name: insert_contains_remove,\n arguments: [\n 0u8,\n 1u8\n ]\n}"}' speculate: the execution was accepted add_next_block: succeeded. additional: - child_outputs: credits.aleo/fee_public: outputs: - - '{"type":"future","id":"2881122525232065385481796721562444359720272068488472880078531341886151580916field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1eakarna6a70pg9r0l9qal20faejwctgur5xt7lnc2a42wj2yssfqc89rk8,\n 11277u64\n ]\n}"}' + - '{"type":"future","id":"3191606140390859923902346999135521074697581448221780792802001082864590116996field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1eakarna6a70pg9r0l9qal20faejwctgur5xt7lnc2a42wj2yssfqc89rk8,\n 11277u64\n ]\n}"}' - child_outputs: credits.aleo/fee_public: outputs: - - '{"type":"future","id":"6719612682279408983981783077880397063556905449118263853724176678274966881762field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1eakarna6a70pg9r0l9qal20faejwctgur5xt7lnc2a42wj2yssfqc89rk8,\n 53072u64\n ]\n}"}' + - '{"type":"future","id":"5979055390005264978560833709807279964627204089794847915828811496749382106218field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1eakarna6a70pg9r0l9qal20faejwctgur5xt7lnc2a42wj2yssfqc89rk8,\n 27572u64\n ]\n}"}' - child_outputs: credits.aleo/fee_public: outputs: - - '{"type":"future","id":"6685223531240969717295379114037138437571065736359141263535338788158408659918field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1eakarna6a70pg9r0l9qal20faejwctgur5xt7lnc2a42wj2yssfqc89rk8,\n 53072u64\n ]\n}"}' + - '{"type":"future","id":"2369910688122343584805292988218981865447881210085467900122008773020689617798field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1eakarna6a70pg9r0l9qal20faejwctgur5xt7lnc2a42wj2yssfqc89rk8,\n 27572u64\n ]\n}"}' - child_outputs: credits.aleo/fee_public: outputs: - - '{"type":"future","id":"6136819185906425044619698216040414457898459958353995998170624664122379782065field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1eakarna6a70pg9r0l9qal20faejwctgur5xt7lnc2a42wj2yssfqc89rk8,\n 53072u64\n ]\n}"}' + - '{"type":"future","id":"1684556726677316491224491400831698251593300210387936038569047294063583175206field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1eakarna6a70pg9r0l9qal20faejwctgur5xt7lnc2a42wj2yssfqc89rk8,\n 27572u64\n ]\n}"}' diff --git a/synthesizer/tests/expectations/vm/execute_and_finalize/mint_and_split.out b/synthesizer/tests/expectations/vm/execute_and_finalize/mint_and_split.out index 770f1ec6a6..7882a55275 100644 --- a/synthesizer/tests/expectations/vm/execute_and_finalize/mint_and_split.out +++ b/synthesizer/tests/expectations/vm/execute_and_finalize/mint_and_split.out @@ -4,7 +4,7 @@ outputs: execute: mint_and_split.aleo/mint: outputs: - - '{"type":"record","id":"6353253438190700833522968952968183605096976752373196500984430371134799540717field","checksum":"8184358533030769026978662540088424648495619580348308369892861662641012895541field","value":"record1qvqspkrp78s2s67slj4wz3waglqsf0vy3748m2jkl5ymtlaz46xelxsgqyxx66trwfhkxun9v35hguerqqpqzqqagf8tzjve9yyrty93x2pc3xw8daz85kc5llwdka0ffyyujz2ep78x68u6n3k3qfrztm5s8kwnjav3f0s7ste3t02c2mmwag5yplsq5an8azw","sender_ciphertext":"4851076334712297276225935856989676746235789679980218212844883843989508401256field"}' + - '{"type":"record","id":"1468168659655366907697532315005585604602235179414698571655118859545131768650field","checksum":"6807819896369708131926098368191156693073473487025184213539045224228545662094field","value":"record1qvqspslmundxglsjhl6l62aq70gkgms6r3qlmtl6resu922sszcpwvcdqyxx66trwfhkxun9v35hguerqqpqzq92jpk9s2ux4q9mw4uhjzc6ja57nkrdvhk4a507wxuezq644r93zq8d77h8639m7shha4fs8qc3n6nrf2eqjsd3plq8q62h9pnlgnfsk87aunl","sender_ciphertext":"1051635936696090036331309351829733592675048904485369308918572030834176000447field"}' speculate: the execution was accepted add_next_block: succeeded. - execute: Commitment '1266307482263846358970326041806201638141701138269282465033372005968041137990field' does not exist @@ -13,6 +13,6 @@ additional: - child_outputs: credits.aleo/fee_public: outputs: - - '{"type":"future","id":"4554638218738710539225481029697366485173135447624464122854964508264596615139field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo19e6k5ferx3k8a9k79xtj4uuaztt2jl4eza7k43pygsu977yazypqqwdmw6,\n 1479u64\n ]\n}"}' + - '{"type":"future","id":"2746364814549419434462217925821184839454131689242696526114638347923889598868field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo19e6k5ferx3k8a9k79xtj4uuaztt2jl4eza7k43pygsu977yazypqqwdmw6,\n 1479u64\n ]\n}"}' - {} - {} diff --git a/synthesizer/tests/expectations/vm/execute_and_finalize/program_callable.out b/synthesizer/tests/expectations/vm/execute_and_finalize/program_callable.out index 2366a5554e..f5b96d7b34 100644 --- a/synthesizer/tests/expectations/vm/execute_and_finalize/program_callable.out +++ b/synthesizer/tests/expectations/vm/execute_and_finalize/program_callable.out @@ -5,10 +5,10 @@ outputs: execute: parent.aleo/foo: outputs: - - '{"type":"public","id":"3914637036303864526243159286220007976533170083569928638487262238892809970742field","value":"aleo16w8t56s7v6ud7vu33fr388ph0dq0c7yhp597cyjt88rr3nultcyqcyk9yy"}' - - '{"type":"public","id":"937398777111453785192036425221737880852307156425401363223955833596768256674field","value":"aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx"}' - - '{"type":"public","id":"3760234756062381156062268377754871790067277250188262172583636143907064923470field","value":"aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx"}' - - '{"type":"public","id":"5488414192083883572281689896150580843935493231906795299005325757111494763599field","value":"aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx"}' + - '{"type":"public","id":"1371061461873926898397004985120437429291848543495585595117110774750728567528field","value":"aleo16w8t56s7v6ud7vu33fr388ph0dq0c7yhp597cyjt88rr3nultcyqcyk9yy"}' + - '{"type":"public","id":"2271453977407703949670413438929595368014167111475673621796723660343376832844field","value":"aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx"}' + - '{"type":"public","id":"1818856551256455036942357442801042851640054236241694259035672566820598837617field","value":"aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx"}' + - '{"type":"public","id":"6413603577239426963318899464999480445574077335417190993162941560670171398658field","value":"aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx"}' speculate: the execution was accepted add_next_block: succeeded. additional: @@ -16,8 +16,8 @@ additional: - child_outputs: child.aleo/foo: outputs: - - '{"type":"public","id":"7280898116097274651284715158779545453186426181228488452909864525015787112960field","value":"aleo16w8t56s7v6ud7vu33fr388ph0dq0c7yhp597cyjt88rr3nultcyqcyk9yy"}' - - '{"type":"public","id":"4075151575211412636182527473997668377132540629423063277503775378409385373898field","value":"aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx"}' + - '{"type":"public","id":"7555245665510220184547435766388558056427845063568367198866117346297304296642field","value":"aleo16w8t56s7v6ud7vu33fr388ph0dq0c7yhp597cyjt88rr3nultcyqcyk9yy"}' + - '{"type":"public","id":"1690164383085980088545085018216177837637344017500691359941048581559415079114field","value":"aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx"}' credits.aleo/fee_public: outputs: - - '{"type":"future","id":"7802751002421695323881906367190483603442707786944479318276597578348108753232field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx,\n 2187u64\n ]\n}"}' + - '{"type":"future","id":"700774998793171775051648190864352012527009927357381492144293789993670813882field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1qr2ha4pfs5l28aze88yn6fhleeythklkczrule2v838uwj65n5gqxt9djx,\n 2187u64\n ]\n}"}' diff --git a/synthesizer/tests/expectations/vm/execute_and_finalize/public_wallet.out b/synthesizer/tests/expectations/vm/execute_and_finalize/public_wallet.out index 9f771050cb..e15664ff12 100644 --- a/synthesizer/tests/expectations/vm/execute_and_finalize/public_wallet.out +++ b/synthesizer/tests/expectations/vm/execute_and_finalize/public_wallet.out @@ -4,14 +4,14 @@ outputs: execute: public_wallet.aleo/init: outputs: - - '{"type":"future","id":"8260497972742778111367097052162295474031012001184713805972943051311817949697field","value":"{\n program_id: public_wallet.aleo,\n function_name: init,\n arguments: [\n {\n program_id: token.aleo,\n function_name: mint_public,\n arguments: [\n aleo1sry3pke49ykrf0aeshf889tr98r4c86p5f4ms766795ssdwfdyqq9jdg0j,\n 10u64\n ]\n }\n \n ]\n}"}' + - '{"type":"future","id":"4301935063856528792770531464680526428860171211855685443863031578636300262555field","value":"{\n program_id: public_wallet.aleo,\n function_name: init,\n arguments: [\n {\n program_id: token.aleo,\n function_name: mint_public,\n arguments: [\n aleo1sry3pke49ykrf0aeshf889tr98r4c86p5f4ms766795ssdwfdyqq9jdg0j,\n 10u64\n ]\n }\n \n ]\n}"}' speculate: the execution was accepted add_next_block: succeeded. additional: - child_outputs: token.aleo/mint_public: outputs: - - '{"type":"future","id":"8286939272855752797035017591517297078431849718370537735860811608893885242485field","value":"{\n program_id: token.aleo,\n function_name: mint_public,\n arguments: [\n aleo1sry3pke49ykrf0aeshf889tr98r4c86p5f4ms766795ssdwfdyqq9jdg0j,\n 10u64\n ]\n}"}' + - '{"type":"future","id":"2202351450567370815910625456669183669038513219567563290642080730073907769504field","value":"{\n program_id: token.aleo,\n function_name: mint_public,\n arguments: [\n aleo1sry3pke49ykrf0aeshf889tr98r4c86p5f4ms766795ssdwfdyqq9jdg0j,\n 10u64\n ]\n}"}' credits.aleo/fee_public: outputs: - - '{"type":"future","id":"7334131816336578548207468749349798727140016102586366303966330861449690845886field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1d3e2je2m2hsxwdsvntvf4jnnlj459ywfry6ch2qwrpy6l6r6yvpq8e88h5,\n 27585u64\n ]\n}"}' + - '{"type":"future","id":"1273455844216943465255870150617668200158395462148075484282990088649186950257field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1d3e2je2m2hsxwdsvntvf4jnnlj459ywfry6ch2qwrpy6l6r6yvpq8e88h5,\n 19085u64\n ]\n}"}' diff --git a/synthesizer/tests/expectations/vm/execute_and_finalize/read_external_mapping.out b/synthesizer/tests/expectations/vm/execute_and_finalize/read_external_mapping.out index d446e24b48..ce2e9b34c3 100644 --- a/synthesizer/tests/expectations/vm/execute_and_finalize/read_external_mapping.out +++ b/synthesizer/tests/expectations/vm/execute_and_finalize/read_external_mapping.out @@ -4,92 +4,92 @@ outputs: execute: relay.aleo/send: outputs: - - '{"type":"record","id":"7988368069920405811122617010802481332270062105651732325916058930730146756095field","checksum":"1952479013890329007616141296701739923115915057943244935571499514613982296383field","value":"record1qvqsqa8wejf3pktamg3d4zjzg60t4dagq3qv9l0t4em98x0l9yz4wtc9qyzxgct5vy3sqqspqp6k9wcd0r5n0zv2zfj64kn6g6lvhd6qmzhyftycf2jptp0z7mqqr24jykrxzjjh327raww8grz503e0kndrqszz65x3pp6tvr8fedqyltlglc","sender_ciphertext":"7280607784450547622085724622408679510223517262895211495317225549467191032501field"}' - - '{"type":"future","id":"4831496979974153421195875947823828412750550539628662893314839630114070713887field","value":"{\n program_id: relay.aleo,\n function_name: send,\n arguments: [\n aleo1f6eg623knp66cwx0926w3plgdgzcmfpgyrzgnjz90mucgs3z7s9qls4upm\n ]\n}"}' + - '{"type":"record","id":"3469204792472709274318580214084659351049823883379307656661473255252784021505field","checksum":"6003440332206336515041988220996944800380409861037282935891051688887515442683field","value":"record1qvqsp3tt8uccxlzashsrxnf96qc8sh2p0cggjk8vt4cjcl6srqt4zhcjqyzxgct5vy3sqqspqrd0vqydmynkv235rmj2x5cwp4fqrheqep9j6spv9p05nml75v2q42z54vem39le6wtce5ffc3f9ae60vn4d5tsmlw0snu4f32lwpss9c2a4dq","sender_ciphertext":"3735591719435492757011851764276832597307317520825612715419091666319352432115field"}' + - '{"type":"future","id":"4999598622359503268426511320342488715088353220705637121568597221605973547422field","value":"{\n program_id: relay.aleo,\n function_name: send,\n arguments: [\n aleo1f6eg623knp66cwx0926w3plgdgzcmfpgyrzgnjz90mucgs3z7s9qls4upm\n ]\n}"}' speculate: the execution was rejected add_next_block: succeeded. - verified: true execute: relay.aleo/send_without_check: outputs: - - '{"type":"record","id":"8059316532341280583834053053840148784965892071763660706105829519617107605765field","checksum":"2298825551909434536504044856679443767307189973371363341224122399603628400614field","value":"record1qvqsp7uvd7gsea34c3arhfjr5setujmdex2hr6c5nvw3wuhjpz3r6uszqyzxgct5vy3sqqspqzac5vgg52z3jshxttd4ekmnq59l39qt9gg3v0y700xtnlqfmq0p9kqrjkktnje0v2cu8d3xa8d0m8dee9umhsmr27qqqz42rlzwtqsqsfmcu5","sender_ciphertext":"7873080142840999142944159025406741234339607472709419402974453002556301425353field"}' - - '{"type":"future","id":"1466198210314069047165522334753605767036936785663542828862455797550717023142field","value":"{\n program_id: relay.aleo,\n function_name: send_without_check,\n arguments: [\n aleo1f6eg623knp66cwx0926w3plgdgzcmfpgyrzgnjz90mucgs3z7s9qls4upm\n ]\n}"}' + - '{"type":"record","id":"5497580162222104008270528028383693547523563761652934289922319048071187061978field","checksum":"7932467712163353347633874015324919315755795598746926005557703707923297029281field","value":"record1qvqspe65cswjf96wsmg4gvgh028lea0yt8d75q5a09vdnrzl4lxck3svqyzxgct5vy3sqqspqrhxgwwce8c8ugkfrcm88j2eyaw5uvy79eyl2x3dq8d0pe3asknqsummp0pdzljl7gk3fz69ag4uv9lfxz2rmtqfmrtqxgatvnnyr2gjxx8qsr","sender_ciphertext":"7911824051387782141865365775420776810970086974551342831558157556850249700476field"}' + - '{"type":"future","id":"1838958035191384683667365565932414128631595248657539275989341365607623702657field","value":"{\n program_id: relay.aleo,\n function_name: send_without_check,\n arguments: [\n aleo1f6eg623knp66cwx0926w3plgdgzcmfpgyrzgnjz90mucgs3z7s9qls4upm\n ]\n}"}' speculate: the execution was accepted add_next_block: succeeded. - verified: true execute: relay.aleo/check_has_registered: outputs: - - '{"type":"future","id":"2177670626770212991926988612892200016040439353062111366356381933892230577888field","value":"{\n program_id: relay.aleo,\n function_name: check_has_registered,\n arguments: [\n aleo1f6eg623knp66cwx0926w3plgdgzcmfpgyrzgnjz90mucgs3z7s9qls4upm\n ]\n}"}' + - '{"type":"future","id":"2879443361121322790837446182687767067029853027753120010212431933713740256998field","value":"{\n program_id: relay.aleo,\n function_name: check_has_registered,\n arguments: [\n aleo1f6eg623knp66cwx0926w3plgdgzcmfpgyrzgnjz90mucgs3z7s9qls4upm\n ]\n}"}' speculate: the execution was rejected add_next_block: succeeded. - verified: true execute: registry.aleo/register: outputs: - - '{"type":"future","id":"3113417577108523663600456494916638919964966001158208014797265008275831629467field","value":"{\n program_id: registry.aleo,\n function_name: register,\n arguments: [\n aleo1f6eg623knp66cwx0926w3plgdgzcmfpgyrzgnjz90mucgs3z7s9qls4upm\n ]\n}"}' + - '{"type":"future","id":"7544787754695053010232199115039799818164929530020624120463079243827608746361field","value":"{\n program_id: registry.aleo,\n function_name: register,\n arguments: [\n aleo1f6eg623knp66cwx0926w3plgdgzcmfpgyrzgnjz90mucgs3z7s9qls4upm\n ]\n}"}' speculate: the execution was accepted add_next_block: succeeded. - verified: true execute: relay.aleo/check_has_registered: outputs: - - '{"type":"future","id":"1703350848802287338721207414343167409705599156052884130496399876478780340060field","value":"{\n program_id: relay.aleo,\n function_name: check_has_registered,\n arguments: [\n aleo1f6eg623knp66cwx0926w3plgdgzcmfpgyrzgnjz90mucgs3z7s9qls4upm\n ]\n}"}' + - '{"type":"future","id":"1603167130361688659208428353062992066107566820944868055505540159828091562443field","value":"{\n program_id: relay.aleo,\n function_name: check_has_registered,\n arguments: [\n aleo1f6eg623knp66cwx0926w3plgdgzcmfpgyrzgnjz90mucgs3z7s9qls4upm\n ]\n}"}' speculate: the execution was accepted add_next_block: succeeded. - verified: true execute: relay.aleo/send: outputs: - - '{"type":"record","id":"7019705233158033397862463604054852462240017015648963823839418103459023359467field","checksum":"6640429124252961209305168193777361983358754657601437727113266554955181047510field","value":"record1qvqspf6yc43x2jdwg6wshazt7uzuk7etqpez0khvelc6s63uzc827kgsqyzxgct5vy3sqqspqqdyxpuk6dv98fzpe4ssg7lkda8pxg67gxktg62smrlwksgy0a4s6cv957l8wdrsn4r2kqc3d4fppny8jzntf7sc8rn3yyuzctx9wygt939qer","sender_ciphertext":"3127340994928047995783372904914993690651335681416405383721056847023403987392field"}' - - '{"type":"future","id":"3826201585361431846590743971353230409815759916830496075267102612167070324224field","value":"{\n program_id: relay.aleo,\n function_name: send,\n arguments: [\n aleo1f6eg623knp66cwx0926w3plgdgzcmfpgyrzgnjz90mucgs3z7s9qls4upm\n ]\n}"}' + - '{"type":"record","id":"2985709493481285351192781918253695753567172588941081875223876497560634201302field","checksum":"3616312832862093012060244472376762550455201362636640194386273703037008138658field","value":"record1qvqsp62za7wh72pnu789pw72t30nwaqjxmf9xk0uvyt0xf76qjhp2cssqyzxgct5vy3sqqspqzxgey9axk54a6zzvm9rfv6sy8zzhaqjwl93z3h0e9epkp7k2q5qx7050yt9qtkv8e6lkxmt7mkeeq94r6wu9gvvjrsqqgnrvafajvcv2pwru9","sender_ciphertext":"3463723916867659596522152247457123983042404579795532215471777100741090613765field"}' + - '{"type":"future","id":"3077368140832887279993226513402668399619465638758240372671782851035659489192field","value":"{\n program_id: relay.aleo,\n function_name: send,\n arguments: [\n aleo1f6eg623knp66cwx0926w3plgdgzcmfpgyrzgnjz90mucgs3z7s9qls4upm\n ]\n}"}' speculate: the execution was accepted add_next_block: succeeded. - verified: true execute: registry.aleo/unregister: outputs: - - '{"type":"future","id":"7725538601531127136662536220663788769690295240695862649177442475416323740861field","value":"{\n program_id: registry.aleo,\n function_name: unregister,\n arguments: [\n aleo1f6eg623knp66cwx0926w3plgdgzcmfpgyrzgnjz90mucgs3z7s9qls4upm\n ]\n}"}' + - '{"type":"future","id":"1267336994654549511613995213112689092692104582872581761772366009248998872771field","value":"{\n program_id: registry.aleo,\n function_name: unregister,\n arguments: [\n aleo1f6eg623knp66cwx0926w3plgdgzcmfpgyrzgnjz90mucgs3z7s9qls4upm\n ]\n}"}' speculate: the execution was accepted add_next_block: succeeded. - verified: true execute: relay.aleo/send: outputs: - - '{"type":"record","id":"959921369465578298957798271224458074648287913439706161748884282792837755766field","checksum":"2601478060564005649172652953857512801885363802859196612984869382561236583871field","value":"record1qvqspka5axladyfnx3j6m2tk3vt63x7u2mma940kf5wsvh59xdjuv5q3qyzxgct5vy3sqqspqrhwlpkd60jsc3yr5xsqchasz8la83lez4u4j3qlzvp285uz5nzsm7xvxe59u9j5rapncfaglaz8lecu0pz6jtextqlguqcy25lawlgtfyaumz","sender_ciphertext":"5199759697243236622508457129711196693870240340526177969116884171527261866027field"}' - - '{"type":"future","id":"3122341733121312071339023730127307311054004252496267855252114291543616014967field","value":"{\n program_id: relay.aleo,\n function_name: send,\n arguments: [\n aleo1f6eg623knp66cwx0926w3plgdgzcmfpgyrzgnjz90mucgs3z7s9qls4upm\n ]\n}"}' + - '{"type":"record","id":"2524806283400497033228002243081060133921978783213181322387122128641708383732field","checksum":"4211221966936690961673993492956926510746207762723789300434554566145580406372field","value":"record1qvqsqaqxdvra785d32459s8a2yv5jjgvxc8rdkpv4q3vlx6cp8ajx3sgqyzxgct5vy3sqqspqrjw303fnlc9k6f5fzcs0z3stcnczatd5l40nvs40ez6xrw9d8ss50tcgyuhwu9mhextrz28pzenmdkyzeyfs5kmt9uhpedsrs8206g244pa2r","sender_ciphertext":"1155783675485352767006730201124024501026789507707830769086689124740990370032field"}' + - '{"type":"future","id":"5300156255687606140535643780698397981525060677383030173094703073006220780498field","value":"{\n program_id: relay.aleo,\n function_name: send,\n arguments: [\n aleo1f6eg623knp66cwx0926w3plgdgzcmfpgyrzgnjz90mucgs3z7s9qls4upm\n ]\n}"}' speculate: the execution was rejected add_next_block: succeeded. additional: - child_outputs: credits.aleo/fee_public: outputs: - - '{"type":"future","id":"3589390014745425368553596668005170223994371788342529645357757014274967046358field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1xe2fps8f9xpdas2q0fqy22uraenk84tvvzetrsyxgnwy6445h59s6wv78x,\n 12364u64\n ]\n}"}' + - '{"type":"future","id":"6407770029496652608634611235124287661118720113988195424442925588056139688957field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1xe2fps8f9xpdas2q0fqy22uraenk84tvvzetrsyxgnwy6445h59s6wv78x,\n 3864u64\n ]\n}"}' - child_outputs: credits.aleo/fee_public: outputs: - - '{"type":"future","id":"3701146412856151989901093015972025634384202289907118341946733967172533797223field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1xe2fps8f9xpdas2q0fqy22uraenk84tvvzetrsyxgnwy6445h59s6wv78x,\n 12392u64\n ]\n}"}' + - '{"type":"future","id":"1559052936509420369913016715667550431877880209642400490069332787235385322319field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1xe2fps8f9xpdas2q0fqy22uraenk84tvvzetrsyxgnwy6445h59s6wv78x,\n 3892u64\n ]\n}"}' - child_outputs: credits.aleo/fee_public: outputs: - - '{"type":"future","id":"6067586553805461002652098047959226436380135730552833805752117986651131431751field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1xe2fps8f9xpdas2q0fqy22uraenk84tvvzetrsyxgnwy6445h59s6wv78x,\n 12149u64\n ]\n}"}' + - '{"type":"future","id":"4043240155660352998266586572529723517345822274904878027831090876776464840217field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1xe2fps8f9xpdas2q0fqy22uraenk84tvvzetrsyxgnwy6445h59s6wv78x,\n 3649u64\n ]\n}"}' - child_outputs: credits.aleo/fee_public: outputs: - - '{"type":"future","id":"5190423787375252433831556836413378994143365939958931399671537580774742441501field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1f6eg623knp66cwx0926w3plgdgzcmfpgyrzgnjz90mucgs3z7s9qls4upm,\n 14542u64\n ]\n}"}' + - '{"type":"future","id":"2480783817359134326256894414953450506931452790228266622574482351896276955399field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1f6eg623knp66cwx0926w3plgdgzcmfpgyrzgnjz90mucgs3z7s9qls4upm,\n 14542u64\n ]\n}"}' - child_outputs: credits.aleo/fee_public: outputs: - - '{"type":"future","id":"6012250561080400779848346517044817117765603129638144755490871744716735055256field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1xe2fps8f9xpdas2q0fqy22uraenk84tvvzetrsyxgnwy6445h59s6wv78x,\n 12149u64\n ]\n}"}' + - '{"type":"future","id":"1121922246740308647099451357707982182353928676863856489108292954533235402551field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1xe2fps8f9xpdas2q0fqy22uraenk84tvvzetrsyxgnwy6445h59s6wv78x,\n 3649u64\n ]\n}"}' - child_outputs: credits.aleo/fee_public: outputs: - - '{"type":"future","id":"2449597468781558451226040811547424712231642896811686133186858221678744551915field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1xe2fps8f9xpdas2q0fqy22uraenk84tvvzetrsyxgnwy6445h59s6wv78x,\n 12364u64\n ]\n}"}' + - '{"type":"future","id":"4047616937700745922908113328668463306067037571319072437653743815958087399529field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1xe2fps8f9xpdas2q0fqy22uraenk84tvvzetrsyxgnwy6445h59s6wv78x,\n 3864u64\n ]\n}"}' - child_outputs: credits.aleo/fee_public: outputs: - - '{"type":"future","id":"4718327215449200025648673668371101175829725629919656989177747688320957470308field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1f6eg623knp66cwx0926w3plgdgzcmfpgyrzgnjz90mucgs3z7s9qls4upm,\n 14546u64\n ]\n}"}' + - '{"type":"future","id":"7321530911167921214177444977369200297737153248861196636283599052811336545406field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1f6eg623knp66cwx0926w3plgdgzcmfpgyrzgnjz90mucgs3z7s9qls4upm,\n 14546u64\n ]\n}"}' - child_outputs: credits.aleo/fee_public: outputs: - - '{"type":"future","id":"5727495718255591791121735702151353671365978909793083114394902593161531025535field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1xe2fps8f9xpdas2q0fqy22uraenk84tvvzetrsyxgnwy6445h59s6wv78x,\n 3864u64\n ]\n}"}' + - '{"type":"future","id":"7514511613682800304484111985363690730830420293688618054184572073347646584115field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1xe2fps8f9xpdas2q0fqy22uraenk84tvvzetrsyxgnwy6445h59s6wv78x,\n 3864u64\n ]\n}"}' diff --git a/synthesizer/tests/expectations/vm/execute_and_finalize/test_branch.out b/synthesizer/tests/expectations/vm/execute_and_finalize/test_branch.out index d88f5dd066..a4875ec064 100644 --- a/synthesizer/tests/expectations/vm/execute_and_finalize/test_branch.out +++ b/synthesizer/tests/expectations/vm/execute_and_finalize/test_branch.out @@ -4,33 +4,33 @@ outputs: execute: test_branch.aleo/run_test: outputs: - - '{"type":"future","id":"3737232062249529150138932693955398881668644103622567558835586062596389824604field","value":"{\n program_id: test_branch.aleo,\n function_name: run_test,\n arguments: [\n 1u8,\n 1u8\n ]\n}"}' + - '{"type":"future","id":"6106585569950414438360489337799100050690125364098997355406750990433066413751field","value":"{\n program_id: test_branch.aleo,\n function_name: run_test,\n arguments: [\n 1u8,\n 1u8\n ]\n}"}' speculate: the execution was rejected add_next_block: succeeded. - verified: true execute: test_branch.aleo/run_test: outputs: - - '{"type":"future","id":"2839212090251948040934158633764862330933299393598341999009181801836696965367field","value":"{\n program_id: test_branch.aleo,\n function_name: run_test,\n arguments: [\n 0u8,\n 1u8\n ]\n}"}' + - '{"type":"future","id":"7275200780186037183036807964364229767729036955012046448515542801448436855044field","value":"{\n program_id: test_branch.aleo,\n function_name: run_test,\n arguments: [\n 0u8,\n 1u8\n ]\n}"}' speculate: the execution was rejected add_next_block: succeeded. - verified: true execute: test_branch.aleo/run_test: outputs: - - '{"type":"future","id":"6861288483994776877281706816951915077787425159195353214277004687152283823579field","value":"{\n program_id: test_branch.aleo,\n function_name: run_test,\n arguments: [\n 0u8,\n 0u8\n ]\n}"}' + - '{"type":"future","id":"6729935225925686376829825047756698138940105959385167022781823671968406951044field","value":"{\n program_id: test_branch.aleo,\n function_name: run_test,\n arguments: [\n 0u8,\n 0u8\n ]\n}"}' speculate: the execution was accepted add_next_block: succeeded. additional: - child_outputs: credits.aleo/fee_public: outputs: - - '{"type":"future","id":"380666872983254596471877288883774301967831022825979316512636463061242206552field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1x3r205zqql5ywy0cqqt74k0r0htuusn0d037ycxe8ftt9ep8hyzsmqz4dh,\n 3500u64\n ]\n}"}' + - '{"type":"future","id":"2480060063861222436156813972262241293645762208069438187904228749398160966602field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1x3r205zqql5ywy0cqqt74k0r0htuusn0d037ycxe8ftt9ep8hyzsmqz4dh,\n 3500u64\n ]\n}"}' - child_outputs: credits.aleo/fee_public: outputs: - - '{"type":"future","id":"1149597548019710132122321749446181378057338947597516816061468439476407924951field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1x3r205zqql5ywy0cqqt74k0r0htuusn0d037ycxe8ftt9ep8hyzsmqz4dh,\n 3500u64\n ]\n}"}' + - '{"type":"future","id":"4073264799848001521009998796784100220277849921364419717140737125317465669764field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1x3r205zqql5ywy0cqqt74k0r0htuusn0d037ycxe8ftt9ep8hyzsmqz4dh,\n 3500u64\n ]\n}"}' - child_outputs: credits.aleo/fee_public: outputs: - - '{"type":"future","id":"2128802402437912236919291162449765933396168235458827108383886305423871549239field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1x3r205zqql5ywy0cqqt74k0r0htuusn0d037ycxe8ftt9ep8hyzsmqz4dh,\n 3500u64\n ]\n}"}' + - '{"type":"future","id":"4995643496661052214451127031245246708209764813936358243440516378760830768547field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1x3r205zqql5ywy0cqqt74k0r0htuusn0d037ycxe8ftt9ep8hyzsmqz4dh,\n 3500u64\n ]\n}"}' diff --git a/synthesizer/tests/expectations/vm/execute_and_finalize/test_rand.out b/synthesizer/tests/expectations/vm/execute_and_finalize/test_rand.out index 51fb5b5e60..753769eea2 100644 --- a/synthesizer/tests/expectations/vm/execute_and_finalize/test_rand.out +++ b/synthesizer/tests/expectations/vm/execute_and_finalize/test_rand.out @@ -4,44 +4,44 @@ outputs: execute: test_rand.aleo/rand_chacha_with_literals: outputs: - - '{"type":"future","id":"3051485481913361775826744416280406419787734940058310766095173488908684408941field","value":"{\n program_id: test_rand.aleo,\n function_name: rand_chacha_with_literals,\n arguments: [\n 0scalar,\n 0group,\n 0u8,\n 2i16,\n 4u32,\n 7i64,\n 8u128,\n 10field\n ]\n}"}' + - '{"type":"future","id":"3634658910127421845796837069200280112930625079693342835137243528288387412789field","value":"{\n program_id: test_rand.aleo,\n function_name: rand_chacha_with_literals,\n arguments: [\n 0scalar,\n 0group,\n 0u8,\n 2i16,\n 4u32,\n 7i64,\n 8u128,\n 10field\n ]\n}"}' speculate: the execution was accepted add_next_block: succeeded. - verified: true execute: test_rand.aleo/rand_chacha_with_struct: outputs: - - '{"type":"future","id":"5324538037254734381263298163254525700334315803949307776069573574643667789515field","value":"{\n program_id: test_rand.aleo,\n function_name: rand_chacha_with_struct,\n arguments: [\n {\n first: 0field,\n second: 0field,\n third: 0field,\n fourth: 0field,\n fifth: 0field\n}\n ]\n}"}' + - '{"type":"future","id":"7862289710662112130225942628540031324056781427629383981063484517663858522227field","value":"{\n program_id: test_rand.aleo,\n function_name: rand_chacha_with_struct,\n arguments: [\n {\n first: 0field,\n second: 0field,\n third: 0field,\n fourth: 0field,\n fifth: 0field\n}\n ]\n}"}' speculate: the execution was accepted add_next_block: succeeded. - verified: true execute: test_rand.aleo/rand_chacha_check: outputs: - - '{"type":"future","id":"3094014759641313043901697261267946468626327163450942596641947962222295207390field","value":"{\n program_id: test_rand.aleo,\n function_name: rand_chacha_check,\n arguments: [\n 0field,\n false\n ]\n}"}' + - '{"type":"future","id":"746545381627269942056971804265088486926111520161800421538758330814778471010field","value":"{\n program_id: test_rand.aleo,\n function_name: rand_chacha_check,\n arguments: [\n 1field,\n false\n ]\n}"}' speculate: the execution was accepted add_next_block: succeeded. - verified: true execute: test_rand.aleo/rand_chacha_check: outputs: - - '{"type":"future","id":"818878742790741579153893179075772445872751227433677932822653185952935999557field","value":"{\n program_id: test_rand.aleo,\n function_name: rand_chacha_check,\n arguments: [\n 1field,\n true\n ]\n}"}' + - '{"type":"future","id":"892614972800960795570418664663732095814901536157182856491855556648676441607field","value":"{\n program_id: test_rand.aleo,\n function_name: rand_chacha_check,\n arguments: [\n 2field,\n true\n ]\n}"}' speculate: the execution was accepted add_next_block: succeeded. additional: - child_outputs: credits.aleo/fee_public: outputs: - - '{"type":"future","id":"121306501224681157597193186410746475033514025731628517615093553600181539558field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1uchf7kruskpp8thlnfeya9qeklcjss27j6rtu74zz7ch559neqystgslsp,\n 601838u64\n ]\n}"}' + - '{"type":"future","id":"4799538299029249285433888723852560497054641016262135753997305300125519631323field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1uchf7kruskpp8thlnfeya9qeklcjss27j6rtu74zz7ch559neqystgslsp,\n 601838u64\n ]\n}"}' - child_outputs: credits.aleo/fee_public: outputs: - - '{"type":"future","id":"372387797317258515886290332539955510428039667099515014147500070605443070643field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1uchf7kruskpp8thlnfeya9qeklcjss27j6rtu74zz7ch559neqystgslsp,\n 26711u64\n ]\n}"}' + - '{"type":"future","id":"7681996792811244843776830498644623363000221546270286991703242217474380323804field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1uchf7kruskpp8thlnfeya9qeklcjss27j6rtu74zz7ch559neqystgslsp,\n 26711u64\n ]\n}"}' - child_outputs: credits.aleo/fee_public: outputs: - - '{"type":"future","id":"819663813855093908211899344309437376318059680751092090723025520667746540822field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1uchf7kruskpp8thlnfeya9qeklcjss27j6rtu74zz7ch559neqystgslsp,\n 26876u64\n ]\n}"}' + - '{"type":"future","id":"3884400416459438166352037482545676257334540936881550166180071401348629161300field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1uchf7kruskpp8thlnfeya9qeklcjss27j6rtu74zz7ch559neqystgslsp,\n 26876u64\n ]\n}"}' - child_outputs: credits.aleo/fee_public: outputs: - - '{"type":"future","id":"8063948964124424703025348936079361092327181010572643865591831235545568925744field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1uchf7kruskpp8thlnfeya9qeklcjss27j6rtu74zz7ch559neqystgslsp,\n 26876u64\n ]\n}"}' + - '{"type":"future","id":"7602640945091934877437281241398779056684447629912969343872906698236123959992field","value":"{\n program_id: credits.aleo,\n function_name: fee_public,\n arguments: [\n aleo1uchf7kruskpp8thlnfeya9qeklcjss27j6rtu74zz7ch559neqystgslsp,\n 26876u64\n ]\n}"}' diff --git a/synthesizer/tests/expectations/vm/execute_and_finalize/unknown_mapping_fail.out b/synthesizer/tests/expectations/vm/execute_and_finalize/unknown_mapping_fail.out index eb8a147f5c..cf557db01e 100644 --- a/synthesizer/tests/expectations/vm/execute_and_finalize/unknown_mapping_fail.out +++ b/synthesizer/tests/expectations/vm/execute_and_finalize/unknown_mapping_fail.out @@ -1,3 +1,3 @@ errors: -- 'Failed to run `VM::deploy for program registry.aleo: Mapping ''foo'' in ''registry.aleo/register'' is not defined.' +- 'Failed to run `VM::deploy for program registry.aleo: Mapping ''foo'' in ''registry.aleo'' is not defined.' outputs: [] diff --git a/synthesizer/tests/test_vm_execute_and_finalize.rs b/synthesizer/tests/test_vm_execute_and_finalize.rs index f5c5a911b2..5d19836a51 100644 --- a/synthesizer/tests/test_vm_execute_and_finalize.rs +++ b/synthesizer/tests/test_vm_execute_and_finalize.rs @@ -76,7 +76,7 @@ fn run_test(test: &ProgramTest) -> serde_yaml::Mapping { let genesis_private_key = PrivateKey::::new(rng).unwrap(); // Initialize the VM. - let (vm, _) = initialize_vm(&genesis_private_key, rng); + let (vm, _) = initialize_vm(&genesis_private_key, test.start_height(), rng); // Fund the additional keys. for key in test.keys() { @@ -370,6 +370,7 @@ fn run_test(test: &ProgramTest) -> serde_yaml::Mapping { #[allow(clippy::type_complexity)] fn initialize_vm( private_key: &PrivateKey, + height: u32, rng: &mut R, ) -> (VM, Vec>>) { // Initialize a VM. @@ -387,6 +388,36 @@ fn initialize_vm( // Add the genesis block to the VM. vm.add_next_block(&genesis).unwrap(); + // If the desired height is greater than zero, add additional blocks to the VM. + for _ in 0..height { + let time_since_last_block = CurrentNetwork::BLOCK_TIME as i64; + let (ratifications, transactions, aborted_transaction_ids, ratified_finalize_operations) = vm + .speculate( + construct_finalize_global_state(&vm), + time_since_last_block, + Some(0u64), + vec![], + &None.into(), + [].into_iter(), + rng, + ) + .unwrap(); + assert!(aborted_transaction_ids.is_empty()); + + let block = construct_next_block( + &vm, + time_since_last_block, + private_key, + ratifications, + transactions, + aborted_transaction_ids, + ratified_finalize_operations, + rng, + ) + .unwrap(); + vm.add_next_block(&block).unwrap(); + } + (vm, records) } diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/arrays_in_finalize.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/arrays_in_finalize.aleo index 451e02306f..fce9d7dc13 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/arrays_in_finalize.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/arrays_in_finalize.aleo @@ -45,6 +45,9 @@ finalize test_arrays: assert.eq r7[0u32][1u32] false; assert.eq r7[0u32][2u32] false; assert.eq r7[0u32][3u32] false; + +constructor: + assert.eq edition 0u16; diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/async_without_finalize_fail.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/async_without_finalize_fail.aleo index caf9ff0c22..6edc901726 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/async_without_finalize_fail.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/async_without_finalize_fail.aleo @@ -11,6 +11,9 @@ function foo: async foo self.caller into r2; add r0 r1 into r3; output r3 as field.private; + +constructor: + assert.eq edition 0u16; diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/bad_constructor_fail.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/bad_constructor_fail.aleo new file mode 100644 index 0000000000..5fa8873161 --- /dev/null +++ b/synthesizer/tests/tests/vm/execute_and_finalize/bad_constructor_fail.aleo @@ -0,0 +1,21 @@ +/* +randomness: 45791624 +cases: + - program: bad_constructor.aleo + function: foo + inputs: ["1field", "2field"] +*/ + +program bad_constructor.aleo; + +constructor: + assert.eq true false; + +function foo: + input r0 as field.private; + input r1 as field.private; + add r0 r1 into r2; + output r2 as field.private; + + + diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/branch_with_future.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/branch_with_future.aleo index 927e4b0db3..d8bcd8c5e5 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/branch_with_future.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/branch_with_future.aleo @@ -33,6 +33,9 @@ finalize foo: input r0 as boolean.public; input r1 as boolean.public; assert.eq r0 r1; + +constructor: + assert.eq edition 0u16; ///////////////////////////////////////////////// @@ -87,3 +90,6 @@ finalize qux: input r0 as child.aleo/foo.future; await r0; await r0; + +constructor: + assert.eq edition 0u16; diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/call_after_async_fail.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/call_after_async_fail.aleo index 272bc951e0..a62df4c028 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/call_after_async_fail.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/call_after_async_fail.aleo @@ -18,6 +18,9 @@ finalize foo: get.or_use count[r0] 0field into r1; add r1 1field into r2; set r2 into count[r0]; + +constructor: + assert.eq edition 0u16; ///////////////////////////////////////////////// @@ -34,4 +37,7 @@ function foo: finalize foo: input r0 as child.aleo/foo.future; await r0; + +constructor: + assert.eq edition 0u16; diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/child_and_parent.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/child_and_parent.aleo index 9b437b6e7f..722428dbe1 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/child_and_parent.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/child_and_parent.aleo @@ -14,6 +14,9 @@ program child.aleo; function foo: output self.caller as address.public; output self.signer as address.public; + +constructor: + assert.eq edition 0u16; ///////////////////////////////////////////////// @@ -27,4 +30,7 @@ function foo: output r1 as address.public; output self.caller as address.public; output self.signer as address.public; + +constructor: + assert.eq edition 0u16; diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/complex_finalization.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/complex_finalization.aleo index 75208a4058..d79c2611e3 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/complex_finalization.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/complex_finalization.aleo @@ -34,6 +34,9 @@ finalize c: get.or_use counts[r0] 0u64 into r1; add r1 1u64 into r2; set r2 into counts[r0]; + +constructor: + assert.eq edition 0u16; ///////////////////////////////////////////////// @@ -51,6 +54,9 @@ finalize d: get.or_use counts[r0] 0u64 into r1; add r1 1u64 into r2; set r2 into counts[r0]; + +constructor: + assert.eq edition 0u16; ///////////////////////////////////////////////// @@ -77,6 +83,9 @@ finalize b: get.or_use counts[r2] 0u64 into r3; add r3 1u64 into r4; set r4 into counts[r2]; + +constructor: + assert.eq edition 0u16; ///////////////////////////////////////////////// @@ -107,6 +116,9 @@ finalize e: get.or_use counts[r3] 0u64 into r4; add r4 1u64 into r5; set r5 into counts[r3]; + +constructor: + assert.eq edition 0u16; ///////////////////////////////////////////////// @@ -147,4 +159,7 @@ finalize a: get.or_use counts[r2] 0u64 into r3; add r3 1u64 into r4; set r4 into counts[r2]; + +constructor: + assert.eq edition 0u16; diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/count_usages.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/count_usages.aleo index 1cf9297bf4..b995e234f4 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/count_usages.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/count_usages.aleo @@ -39,6 +39,9 @@ finalize sub_and_count: get.or_use uses[r0] 0i64 into r1; add r1 1i64 into r2; set r2 into uses[r0]; + +constructor: + assert.eq edition 0u16; ///////////////////////////////////////////////// @@ -63,3 +66,6 @@ finalize add_and_subtract: assert.eq r0[0u32] r1[0u32]; await r0; await r1; + +constructor: + assert.eq edition 0u16; diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/external_read_with_local_fail.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/external_read_with_local_fail.aleo index 56a3c02c9b..e0e6bb822f 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/external_read_with_local_fail.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/external_read_with_local_fail.aleo @@ -15,6 +15,9 @@ function register: finalize register: input r0 as address.public; set true into users[r0]; + +constructor: + assert.eq edition 0u16; ///////////////////////////////////////////////// @@ -36,3 +39,6 @@ finalize send: input r0 as address.public; get relay.aleo/users[r0] into r1; assert.eq r1 true; + +constructor: + assert.eq edition 0u16; diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/future_not_all_awaited_fail.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/future_not_all_awaited_fail.aleo index 21f282a4d7..b53000d884 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/future_not_all_awaited_fail.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/future_not_all_awaited_fail.aleo @@ -28,6 +28,9 @@ finalize boo: get.or_use count[r0] 0field into r1; add r1 1field into r2; set r2 into count[r0]; + +constructor: + assert.eq edition 0u16; ///////////////////////////////////////////////// @@ -57,4 +60,7 @@ finalize foo: await r2; await r0; await r5; + +constructor: + assert.eq edition 0u16; diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/future_not_all_passed_to_async_call_fail.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/future_not_all_passed_to_async_call_fail.aleo index a6b168e07c..bb818a147f 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/future_not_all_passed_to_async_call_fail.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/future_not_all_passed_to_async_call_fail.aleo @@ -28,6 +28,9 @@ finalize boo: get.or_use count[r0] 0field into r1; add r1 1field into r2; set r2 into count[r0]; + +constructor: + assert.eq edition 0u16; ///////////////////////////////////////////////// @@ -56,4 +59,7 @@ finalize foo: await r3; await r2; await r0; + +constructor: + assert.eq edition 0u16; diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/future_out_of_order.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/future_out_of_order.aleo index fa23c85051..378b175cf4 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/future_out_of_order.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/future_out_of_order.aleo @@ -31,6 +31,9 @@ finalize boo: get.or_use count[r0] 0field into r1; add r1 1field into r2; set r2 into count[r0]; + +constructor: + assert.eq edition 0u16; ///////////////////////////////////////////////// @@ -61,4 +64,7 @@ finalize foo: await r2; await r0; await r5; + +constructor: + assert.eq edition 0u16; diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/good_constructor.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/good_constructor.aleo new file mode 100644 index 0000000000..63c8214065 --- /dev/null +++ b/synthesizer/tests/tests/vm/execute_and_finalize/good_constructor.aleo @@ -0,0 +1,48 @@ +/* +randomness: 45791624 +cases: + - program: good_constructor.aleo + function: check + inputs: [] +*/ + +import credits.aleo; + +program good_constructor.aleo; + +mapping data: + key as u8.public; + value as u8.public; + +constructor: + assert.eq edition 0u16; + assert.eq credits.aleo/edition 0u16; + cast 66u8 96u8 158u8 151u8 29u8 229u8 98u8 162u8 249u8 193u8 81u8 68u8 81u8 245u8 4u8 25u8 30u8 238u8 240u8 107u8 121u8 228u8 148u8 181u8 130u8 212u8 178u8 214u8 26u8 4u8 155u8 54u8 into r0 as [u8; 32u32]; + assert.eq credits.aleo/checksum r0; + assert.neq checksum r0; + contains data[0u8] into r1; // Check `contains` without value + assert.eq r1 false; + get.or_use data[0u8] 8u8 into r2; // Check `get.or_use` without value + assert.eq r2 8u8; + set 1u8 into data[0u8]; // Check `set` + contains data[0u8] into r3; // Check `contains` with value + assert.eq r3 true; + get.or_use data[0u8] 0u8 into r4; // Check `get.or_use` without value + assert.eq r4 1u8; + get data[0u8] into r5; // Check `get` with value + assert.eq r5 1u8; + remove data[0u8]; // Check `remove` + contains data[0u8] into r6; // Check `contains` after removal + assert.eq r6 false; + set 1u8 into data[0u8]; // Final set + + +function check: + async check into r0; + output r0 as good_constructor.aleo/check.future; +finalize check: + get data[0u8] into r0; + assert.eq r0 1u8; + + + diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/hello.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/hello.aleo index 8824663cc9..3ea0cee888 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/hello.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/hello.aleo @@ -36,3 +36,6 @@ finalize goodbye: input r1 as u32.public; add r0 r1 into r2; assert.neq r1 r2; + +constructor: + assert.eq edition 0u16; diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/ignore_finalize_fail.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/ignore_finalize_fail.aleo index 7514c7773c..084637f8c0 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/ignore_finalize_fail.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/ignore_finalize_fail.aleo @@ -18,6 +18,9 @@ finalize foo: get.or_use count[r0] 0field into r1; add r1 1field into r2; set r2 into count[r0]; + +constructor: + assert.eq edition 0u16; ///////////////////////////////////////////////// @@ -31,3 +34,6 @@ function foo: call child.aleo/foo into r2; add r0 r1 into r3; output r3 as field.private; + +constructor: + assert.eq edition 0u16; diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/interleave_async_and_non_async.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/interleave_async_and_non_async.aleo index a06d369a3a..c050176e9d 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/interleave_async_and_non_async.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/interleave_async_and_non_async.aleo @@ -48,6 +48,9 @@ finalize save_inner_rand: function dummy: +constructor: + assert.eq edition 0u16; + ///////////////////////////////////////////////// import inner.aleo; @@ -87,6 +90,9 @@ finalize save_mid_rand_2: function dummy: +constructor: + assert.eq edition 0u16; + ///////////////////////////////////////////////// @@ -144,3 +150,6 @@ finalize call_mid_3: function dummy: +constructor: + assert.eq edition 0u16; + diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/last_reg_is_not_future_fail.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/last_reg_is_not_future_fail.aleo index 0a0f2ea01b..a1f37200a9 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/last_reg_is_not_future_fail.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/last_reg_is_not_future_fail.aleo @@ -22,4 +22,7 @@ finalize foo: get.or_use count[r0] 0field into r1; add r1 1field into r2; set r2 into count[r0]; + +constructor: + assert.eq edition 0u16; diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/many_input_and_output.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/many_input_and_output.aleo index 94f680ded5..a036506394 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/many_input_and_output.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/many_input_and_output.aleo @@ -97,6 +97,9 @@ function inner_trash_private: output r13 as u8.private; output r14 as u8.private; output r15 as u8.private; + +constructor: + assert.eq edition 0u16; ///////////////////////////////////////////////// @@ -224,3 +227,6 @@ function make_trash_call_inner_private: call child.aleo/inner_trash_private 0u8 1u8 2u8 3u8 4u8 5u8 6u8 7u8 8u8 9u8 10u8 11u8 12u8 13u8 14u8 15u8 into r16 r17 r18 r19 r20 r21 r22 r23 r24 r25 r26 r27 r28 r29 r30 r31; call child.aleo/inner_trash_private 0u8 1u8 2u8 3u8 4u8 5u8 6u8 7u8 8u8 9u8 10u8 11u8 12u8 13u8 14u8 15u8 into r32 r33 r34 r35 r36 r37 r38 r39 r40 r41 r42 r43 r44 r45 r46 r47; call child.aleo/inner_trash_private 0u8 1u8 2u8 3u8 4u8 5u8 6u8 7u8 8u8 9u8 10u8 11u8 12u8 13u8 14u8 15u8 into r48 r49 r50 r51 r52 r53 r54 r55 r56 r57 r58 r59 r60 r61 r62 r63; + +constructor: + assert.eq edition 0u16; diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/mapping_operations.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/mapping_operations.aleo index 385ac1cf0d..3f03c5e74e 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/mapping_operations.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/mapping_operations.aleo @@ -47,5 +47,8 @@ function empty_remove: finalize empty_remove: input r0 as u8.public; remove data[r0]; + +constructor: + assert.eq edition 0u16; diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/mint_and_split.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/mint_and_split.aleo index c8163d2990..496131827c 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/mint_and_split.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/mint_and_split.aleo @@ -37,3 +37,6 @@ function split: cast r0.owner r2 into r4 as credits.record; output r3 as credits.record; output r4 as credits.record; + +constructor: + assert.eq edition 0u16; diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/multiple_async_fail.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/multiple_async_fail.aleo index 7a186c0f02..9764f69518 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/multiple_async_fail.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/multiple_async_fail.aleo @@ -19,6 +19,9 @@ finalize foo: add r1 1field into r2; set r2 into count[r0]; +constructor: + assert.eq edition 0u16; + ///////////////////////////////////////////////// import child.aleo; @@ -36,3 +39,5 @@ finalize foo: input r0 as child.aleo/foo.future; await r0; +constructor: + assert.eq edition 0u16; diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/no_import_external_read_fail.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/no_import_external_read_fail.aleo index 3a64c123d1..ad2c20aa1d 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/no_import_external_read_fail.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/no_import_external_read_fail.aleo @@ -15,6 +15,9 @@ function register: finalize register: input r0 as address.public; set true into users[r0]; + +constructor: + assert.eq edition 0u16; ///////////////////////////////////////////////// @@ -30,3 +33,6 @@ finalize send: input r0 as address.public; get registry.aleo/users[r0] into r1; assert.eq r1 true; + +constructor: + assert.eq edition 0u16; diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/output_child_without_async_fail.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/output_child_without_async_fail.aleo index 7d34f0c060..937a1e7845 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/output_child_without_async_fail.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/output_child_without_async_fail.aleo @@ -18,6 +18,9 @@ finalize foo: get.or_use count[r0] 0field into r1; add r1 1field into r2; set r2 into count[r0]; + +constructor: + assert.eq edition 0u16; ///////////////////////////////////////////////// @@ -28,5 +31,8 @@ program parent.aleo; function foo: call child.aleo/foo into r0; output r2 as child.aleo/foo.future; + +constructor: + assert.eq edition 0u16; diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/program_callable.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/program_callable.aleo index c8c1062efc..d11ea2b802 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/program_callable.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/program_callable.aleo @@ -15,6 +15,9 @@ function foo: assert.neq self.caller self.signer; output self.caller as address.public; output self.signer as address.public; + +constructor: + assert.eq edition 0u16; ///////////////////////////////////////////////// @@ -28,4 +31,7 @@ function foo: output r1 as address.public; output self.caller as address.public; output self.signer as address.public; + +constructor: + assert.eq edition 0u16; diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/public_wallet.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/public_wallet.aleo index fb2d58e0f4..808ba9ef77 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/public_wallet.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/public_wallet.aleo @@ -42,6 +42,9 @@ finalize mint_public: add r2 r1 into r3; // Set `r3` into `account[r0]`. set r3 into account[r0]; + +constructor: + assert.eq edition 0u16; ///////////////////////////////////////////////// @@ -58,3 +61,6 @@ function init: finalize init: input r0 as token.aleo/mint_public.future; await r0; + +constructor: + assert.eq edition 0u16; diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/read_external_mapping.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/read_external_mapping.aleo index 0dfdf9e79f..04e6f7e9ab 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/read_external_mapping.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/read_external_mapping.aleo @@ -52,6 +52,9 @@ function unregister: finalize unregister: input r0 as address.public; set false into users[r0]; + +constructor: + assert.eq edition 0u16; ///////////////////////////////////////////////// @@ -98,3 +101,6 @@ finalize send_without_check: input r0 as address.public; get.or_use registry.aleo/users[r0] true into r1; assert.eq r1 true; + +constructor: + assert.eq edition 0u16; \ No newline at end of file diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/test_branch.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/test_branch.aleo index 797a5e1bd8..649161d10f 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/test_branch.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/test_branch.aleo @@ -29,4 +29,7 @@ finalize run_test: branch.neq r1 1u8 to exit_two; assert.eq true false; position exit_two; + +constructor: + assert.eq edition 0u16; diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/test_rand.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/test_rand.aleo index 530d359f1f..a0b321f796 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/test_rand.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/test_rand.aleo @@ -9,10 +9,10 @@ cases: inputs: ["{ first: 0field, second: 0field, third: 0field, fourth: 0field, fifth: 0field }"] - program: test_rand.aleo function: rand_chacha_check - inputs: [0field, false] + inputs: [1field, false] - program: test_rand.aleo function: rand_chacha_check - inputs: [1field, true] + inputs: [2field, true] */ program test_rand.aleo; @@ -90,3 +90,6 @@ finalize rand_chacha_with_literals: rand.chacha r4 r5 into r29 as u32; rand.chacha r5 r6 into r30 as i64; rand.chacha r6 r7 into r31 as u128; + +constructor: + assert.eq edition 0u16; diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/timelock.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/timelock.aleo index 7cb5741346..d7c9e74fa5 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/timelock.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/timelock.aleo @@ -1,4 +1,5 @@ /* +start_height: 0 cases: - program: timelock.aleo function: lock diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/unknown_external_mapping_fail.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/unknown_external_mapping_fail.aleo index 05397274d7..d5cfd99bb4 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/unknown_external_mapping_fail.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/unknown_external_mapping_fail.aleo @@ -1,4 +1,5 @@ /* +start_height: 0 cases: [] */ diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/unknown_mapping_fail.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/unknown_mapping_fail.aleo index f344a08a36..f350c42f02 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/unknown_mapping_fail.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/unknown_mapping_fail.aleo @@ -1,4 +1,5 @@ /* +start_height: 0 cases: [] */ diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/unused_position.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/unused_position.aleo index b4a0e5da27..be85ae36f1 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/unused_position.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/unused_position.aleo @@ -1,4 +1,5 @@ /* +start_height: 0 cases: - program: unused_position.aleo function: foo diff --git a/synthesizer/tests/tests/vm/execute_and_finalize/user_callable.aleo b/synthesizer/tests/tests/vm/execute_and_finalize/user_callable.aleo index 9d0ff034b5..241feb4221 100644 --- a/synthesizer/tests/tests/vm/execute_and_finalize/user_callable.aleo +++ b/synthesizer/tests/tests/vm/execute_and_finalize/user_callable.aleo @@ -1,4 +1,5 @@ /* +start_height: 0 randomness: 45791624 cases: - program: child.aleo diff --git a/synthesizer/tests/utilities/tests/program_test.rs b/synthesizer/tests/utilities/tests/program_test.rs index 1960b95132..65d4fe46b7 100644 --- a/synthesizer/tests/utilities/tests/program_test.rs +++ b/synthesizer/tests/utilities/tests/program_test.rs @@ -21,11 +21,11 @@ use snarkvm_synthesizer::program::Program; use anyhow::{Result, bail}; use itertools::Itertools; use serde_yaml::{Mapping, Sequence, Value}; +use snarkvm_console::{network::ConsensusVersion, prelude::Network}; use std::{ path::{Path, PathBuf}, str::FromStr, }; - // TODO: Handle tests where the execution panics or fails. // One approach is to create an enum `ExpectedOutput` which can be `Ok(Vec)` or `Err(String)`. @@ -46,6 +46,8 @@ pub struct ProgramTest { randomness: Option, /// Additional keys for the test. keys: Vec>, + /// The start height for the test. + start_height: u32, } impl ProgramTest { @@ -69,6 +71,11 @@ impl ProgramTest { &self.keys } + /// Returns the start height for the test. + pub fn start_height(&self) -> u32 { + self.start_height + } + /// Returns the path to the expectation file. pub fn path(&self) -> &PathBuf { &self.path @@ -97,6 +104,15 @@ impl ExpectedTest for ProgramTest { // If the `randomness` field is present in the config, parse it as a `u64`. let randomness = test_config.get("randomness").map(|value| value.as_u64().expect("`randomness` must be a u64")); + // If the `start_height` field is present in the config, parse it as a `u32`. + // Otherwise use the latest consensus height as the default. + let start_height = test_config + .get("start_height") + .map(|value| value.as_u64().expect("`start_height` must be a u32")) + .unwrap_or( + CurrentNetwork::CONSENSUS_HEIGHT(ConsensusVersion::latest()).expect("Expected consensus height") as u64 + ) as u32; + // If the `keys` field is present in the config, parse it as a sequence of `PrivateKey`s. let keys = match test_config.get("keys") { None => Vec::new(), @@ -136,7 +152,7 @@ impl ExpectedTest for ProgramTest { } }; - Self { programs, cases, expected, path, rewrite, randomness, keys } + Self { programs, cases, expected, path, rewrite, randomness, keys, start_height } } fn check(&self, output: &Self::Output) -> Result<()> { diff --git a/vm/file/manifest.rs b/vm/file/manifest.rs index 5e0ded0e81..f84a165d8c 100644 --- a/vm/file/manifest.rs +++ b/vm/file/manifest.rs @@ -18,6 +18,8 @@ use crate::{ synthesizer::Program, }; +use snarkvm_circuit::prelude::IndexMap; + use anyhow::{Result, anyhow, ensure}; use core::str::FromStr; use std::{ @@ -33,6 +35,8 @@ pub struct Manifest { path: PathBuf, /// The program ID. program_id: ProgramID, + /// The program editions. + editions: IndexMap, u16>, } impl Manifest { @@ -49,7 +53,8 @@ impl Manifest { "program": "{id}", "version": "0.0.0", "description": "", - "license": "MIT" + "license": "MIT", + "editions": "{{}}" }} "# ); @@ -63,7 +68,7 @@ impl Manifest { File::create(&path)?.write_all(manifest_string.as_bytes())?; // Return the manifest file. - Ok(Self { path, program_id: *id }) + Ok(Self { path, program_id: *id, editions: IndexMap::new() }) } /// Opens the manifest file for reading. @@ -86,8 +91,20 @@ impl Manifest { // Ensure the program name is valid. ensure!(!Program::is_reserved_keyword(id.name()), "Program name is invalid (reserved): {id}"); + // Initialize storage for the program editions. + let mut editions = IndexMap::, u16>::new(); + + // Retrieve the editions in the manifest, if they exist. + if let Some(editions_object) = json["editions"].as_object() { + for (id_string, value) in editions_object { + let id = ProgramID::from_str(id_string)?; + let value = u16::try_from(value.as_u64().ok_or_else(|| anyhow!("The edition must be a number."))?)?; + editions.insert(id, value); + } + } + // Return the manifest file. - Ok(Self { path, program_id: id }) + Ok(Self { path, program_id: id, editions }) } /// Returns `true` if the manifest file exists at the given path. @@ -112,4 +129,9 @@ impl Manifest { pub const fn program_id(&self) -> &ProgramID { &self.program_id } + + /// Returns the program editions. + pub const fn editions(&self) -> &IndexMap, u16> { + &self.editions + } } diff --git a/vm/package/deploy.rs b/vm/package/deploy.rs index 07039eee45..a6d5173b94 100644 --- a/vm/package/deploy.rs +++ b/vm/package/deploy.rs @@ -123,28 +123,13 @@ impl Package { #[cfg(feature = "aleo-cli")] println!("⏳ Deploying '{}'...\n", program_id.to_string().bold()); - // Construct the process. - let mut process = Process::::load()?; - - // Add program imports to the process. - let imports_directory = self.imports_directory(); - program.imports().keys().try_for_each(|program_id| { - // TODO (howardwu): Add the following checks: - // 1) the imported program ID exists *on-chain* (for the given network) - // 2) the AVM bytecode of the imported program matches the AVM bytecode of the program *on-chain* - // 3) consensus performs the exact same checks (in `verify_deployment`) - - // Open the Aleo program file. - let import_program_file = AleoFile::open(&imports_directory, program_id, false)?; - // Add the import program. - process.add_program(import_program_file.program())?; - Ok::<_, Error>(()) - })?; + // Get the process. + let process = self.get_process()?; // Initialize the RNG. let rng = &mut rand::thread_rng(); // Compute the deployment. - let deployment = process.deploy::(program, rng).unwrap(); + let deployment = process.get_stack(program_id)?.deploy::(rng).unwrap(); match endpoint { Some(ref endpoint) => { @@ -167,9 +152,6 @@ impl Package { #[cfg(test)] mod tests { - use super::*; - - type CurrentNetwork = snarkvm_console::network::MainnetV0; type CurrentAleo = snarkvm_circuit::network::AleoV0; #[test] @@ -181,7 +163,7 @@ mod tests { let deployment = package.deploy::(None).unwrap(); // Ensure the deployment edition matches. - assert_eq!(::EDITION, deployment.edition()); + assert_eq!(0, deployment.edition()); // Ensure the deployment program ID matches. assert_eq!(package.program().id(), deployment.program_id()); // Ensure the deployment program matches. @@ -200,7 +182,7 @@ mod tests { let deployment = package.deploy::(None).unwrap(); // Ensure the deployment edition matches. - assert_eq!(::EDITION, deployment.edition()); + assert_eq!(0, deployment.edition()); // Ensure the deployment program ID matches. assert_eq!(package.program().id(), deployment.program_id()); // Ensure the deployment program matches. diff --git a/vm/package/mod.rs b/vm/package/mod.rs index 24d90858bb..1aae38b761 100644 --- a/vm/package/mod.rs +++ b/vm/package/mod.rs @@ -42,7 +42,7 @@ use crate::{ }, }; -use anyhow::{Error, Result, bail, ensure}; +use anyhow::{Result, bail, ensure}; use core::str::FromStr; use rand::{CryptoRng, Rng}; use std::path::{Path, PathBuf}; @@ -153,29 +153,50 @@ impl Package { /// Returns a new process for the package. pub fn get_process(&self) -> Result> { - // Create the process. + // Load the default process. let mut process = Process::load()?; - - // Prepare the imports directory. + // Get the imported programs. let imports_directory = self.imports_directory(); - - // Initialize the 'credits.aleo' program ID. - let credits_program_id = ProgramID::::from_str("credits.aleo")?; - - // Add all import programs (in order) to the process. - self.program().imports().keys().try_for_each(|program_id| { - // Don't add `credits.aleo` as the process is already loaded with it. - if program_id != &credits_program_id { + let mut programs = self + .program() + .imports() + .keys() + .map(|program_id| { + // TODO (howardwu): Add the following checks: + // 1) the imported program ID exists *on-chain* (for the given network) + // 2) the AVM bytecode of the imported program matches the AVM bytecode of the program *on-chain* + // 3) consensus performs the exact same checks (in `verify_deployment`) // Open the Aleo program file. let import_program_file = AleoFile::open(&imports_directory, program_id, false)?; - // Add the import program. - process.add_program(import_program_file.program())?; - } - Ok::<_, Error>(()) - })?; - - // Add the program to the process. - process.add_program(self.program())?; + // Get the program. + Ok(import_program_file.program().clone()) + }) + .collect::>>()?; + // Add the main program. + programs.push(self.program().clone()); + + // Get the editions for the programs, if specified in the manifest. + let programs_and_editions = programs + .into_iter() + .map(|program| { + // Get the program ID. + let program_id = program.id(); + // Get the edition, if specified. + if let Some(edition) = self.manifest_file.editions().get(program_id) { + (program, *edition) + } else { + #[cfg(feature = "aleo-cli")] + println!( + " Could not find an edition for '{}' in the manifest, using edition 0...\n", + program_id.to_string().bold() + ); + (program, 0) + } + }) + .collect::>(); + + // Load the programs. + process.add_programs_with_editions(&programs_and_editions)?; Ok(process) }