From 3d708ea8e989cc765844084dd3b28fdea71977af Mon Sep 17 00:00:00 2001 From: Mohammad Fawaz Date: Mon, 28 Sep 2026 11:57:44 -0400 Subject: [PATCH 01/10] Require trusted pins for network bytecode --- crates/leo/src/cli/cli.rs | 340 ++++++++++- crates/leo/src/cli/commands/add.rs | 3 +- crates/leo/src/cli/commands/build.rs | 6 +- crates/leo/src/cli/commands/common/query.rs | 14 +- crates/leo/src/cli/commands/common/util.rs | 33 +- crates/leo/src/cli/commands/deploy.rs | 3 +- crates/leo/src/cli/commands/execute.rs | 5 +- crates/leo/src/cli/commands/remove.rs | 2 +- crates/leo/src/cli/commands/run.rs | 5 +- crates/leo/src/cli/commands/synthesize.rs | 2 +- crates/leo/src/cli/commands/upgrade.rs | 87 +-- crates/leo/src/cli/helpers/context.rs | 28 +- crates/package/README.md | 37 ++ crates/package/src/compilation_unit.rs | 182 +++--- crates/package/src/errors.rs | 10 + crates/package/src/lock.rs | 136 ++++- crates/package/src/package.rs | 113 +++- crates/package/src/tests.rs | 558 +++++++++++++++++- .../cli/network_dependency/COMMANDS | 27 + .../cli/test_add_dependency/STDERR | 4 +- tests/expectations/cli/test_deploy/COMMANDS | 18 + .../cli/test_deploy_rename/COMMANDS | 18 + .../cli/test_git_dependency/STDOUT | 4 +- tests/expectations/cli/test_json/COMMANDS | 18 + tests/tests/cli/network_dependency/COMMANDS | 27 + tests/tests/cli/test_deploy/COMMANDS | 18 + tests/tests/cli/test_deploy_rename/COMMANDS | 18 + tests/tests/cli/test_json/COMMANDS | 18 + 28 files changed, 1484 insertions(+), 250 deletions(-) diff --git a/crates/leo/src/cli/cli.rs b/crates/leo/src/cli/cli.rs index a08e08b7be9..5cb8f17fecc 100644 --- a/crates/leo/src/cli/cli.rs +++ b/crates/leo/src/cli/cli.rs @@ -49,6 +49,9 @@ pub struct CLI { #[clap(long, global = true, help = "Path to aleo program registry")] pub home: Option, + #[clap(long, global = true, help = "Read trusted network program pins from this lock file")] + network_lock: Option, + #[clap(short = 'p', long = "package", global = true, help = "Target a specific workspace member by name")] pub package: Option, } @@ -236,7 +239,9 @@ pub fn run_with_args(cli: CLI) -> Result<()> { // Get custom root folder and create context for it. // If not specified, default context will be created in cwd. - let context = handle_error(Context::new(cli.path.clone(), cli.home, false, cli.package.clone())); + let mut context = handle_error(Context::new(cli.path.clone(), cli.home, false, cli.package.clone())); + context.network_lock = + cli.network_lock.map(std::path::absolute).transpose().map_err(crate::errors::cli_io_error)?; let command_name = cli.command.name(); let mut command_output: Option = None; @@ -335,10 +340,276 @@ mod tests { use serial_test::serial; use std::{env::temp_dir, path::PathBuf}; - // An unreachable endpoint with no retries stands in for a program that isn't on the network. + #[test] + fn network_lock_is_global_and_survives_member_context() { + use crate::cli::context::Context; + + for args in [vec!["leo", "--network-lock", "trusted.lock", "run", "example.aleo/main"], vec![ + "leo", + "run", + "example.aleo/main", + "--network-lock", + "trusted.lock", + ]] { + let cli = CLI::try_parse_from(args).expect("The global lock option must parse"); + assert_eq!(cli.network_lock, Some(PathBuf::from("trusted.lock"))); + let mut context = Context::new(None, None, false, None).expect("The context must be valid"); + context.network_lock = + cli.network_lock.map(std::path::absolute).transpose().expect("The lock path must resolve"); + assert_eq!(context.with_path(PathBuf::from("member")).network_lock, context.network_lock); + } + } + + #[test] + fn remote_program_requires_a_trusted_pin_before_fetch() { + use crate::cli::{context::Context, load_pinned_programs_from_network}; + use snarkvm::prelude::{ProgramID, TestnetV0}; + + let root = temp_dir().join(format!("leo_remote_pin_{}", std::process::id())); + std::fs::create_dir_all(&root).expect("The temporary directory must be created"); + let context = + Context::new(Some(root.clone()), Some(root.join("cache")), false, None).expect("The context must be valid"); + create_session_if_not_set_then(|_| { + let program = "unpinned.aleo".parse::>().expect("The program ID must parse"); + let error = + load_pinned_programs_from_network(&context, program, NetworkName::TestnetV0, "http://localhost:1", 0) + .expect_err("An unpinned remote program must fail"); + assert!(error.to_string().contains("trusted checksum pin"), "{error}"); + }); + assert!(!root.join("cache").exists(), "A missing pin must not create the cache"); + std::fs::remove_dir_all(root).expect("The temporary directory must be removed"); + } + + #[test] + fn network_lock_missing_override_does_not_use_project_pins() { + use crate::cli::context::Context; + + let root = temp_dir().join(format!("leo_pin_override_{}", std::process::id())); + std::fs::create_dir_all(&root).expect("The temporary directory must be created"); + std::fs::write(root.join("leo.lock"), r#"{"version":1,"git":[]}"#).expect("The project lock must be written"); + let mut context = Context::new(Some(root.clone()), None, false, None).expect("The context must be valid"); + context.network_lock = Some(root.join("absent.lock")); + assert!(context.network_pins().is_err(), "An explicit missing lock must fail"); + std::fs::remove_dir_all(root).expect("The temporary directory must be removed"); + } + + #[test] + fn upgrade_yes_and_no_cache_do_not_allow_unpinned_bytecode_imports() { + use crate::cli::{Command, context::Context}; + + let root = temp_dir().join(format!("leo_upgrade_pin_{}", std::process::id())); + std::fs::create_dir_all(root.join("cache")).expect("The temporary directory must be created"); + let source = root.join("local.aleo"); + std::fs::write( + &source, + r"import unpinned.aleo; +program local.aleo; +function main: + input r0 as u32.public; + output r0 as u32.public; +", + ) + .expect("The local program must be written"); + let cli = CLI::try_parse_from([ + "leo", + "upgrade", + "--yes", + "--no-cache", + "--network", + "testnet", + "--endpoint", + "http://localhost:1", + "--network-retries", + "0", + ]) + .expect("The upgrade options must parse"); + let Commands::Upgrade { command } = cli.command else { panic!("Expected upgrade command") }; + let context = + Context::new(Some(source), Some(root.join("cache")), false, None).expect("The context must be valid"); + create_session_if_not_set_then(|_| { + let error = command.prelude(context).expect_err("The remote import must require a pin"); + assert!(error.to_string().contains("trusted checksum pin"), "{error}"); + }); + assert!(!root.join("cache/registry").exists()); + std::fs::remove_dir_all(root).expect("The temporary directory must be removed"); + } + + #[test] + fn network_pins_use_the_workspace_root() { + use crate::cli::context::Context; + + let root = temp_dir().join(format!("leo_workspace_pins_{}", std::process::id())); + std::fs::create_dir_all(root.join("member")).expect("The member directory must be created"); + std::fs::write(root.join("workspace.json"), r#"{"members":["member"]}"#) + .expect("The workspace file must be written"); + let lock = serde_json::json!({"version":2,"network":[{ + "name":"trusted.aleo","network":"testnet","edition":7,"checksum":vec![0;32] + }]}); + std::fs::write(root.join("leo.lock"), lock.to_string()).expect("The root lock must be written"); + std::fs::write(root.join("member/leo.lock"), "invalid member lock").expect("The member lock must be written"); + let context = Context::new(Some(root.join("member")), None, false, None).expect("The context must be valid"); + let pins = context.network_pins().expect("The workspace lock must load"); + assert_eq!( + pins.network_pin("trusted.aleo", NetworkName::TestnetV0, None) + .expect("The workspace pin must exist") + .edition, + 7 + ); + std::fs::remove_dir_all(root).expect("The temporary directory must be removed"); + } + + #[test] + fn network_lock_override_verifies_import_without_changing_project_pins() { + use leo_package::Package; + use snarkvm::prelude::{Program, TestnetV0}; + + let root = temp_dir().join(format!("leo_override_import_{}", std::process::id())); + test_helpers::scaffold_minimal_member(&root, "consumer"); + let project = root.join("consumer"); + let manifest_path = project.join("program.json"); + let mut manifest = leo_package::Manifest::read_from_file(&manifest_path).expect("The manifest must load"); + manifest.dependencies = Some(vec![leo_package::Dependency { + name: "trusted.aleo".into(), + location: leo_package::Location::Network, + ..Default::default() + }]); + manifest.write_to_file(&manifest_path).expect("The manifest must be written"); + let source = + "program trusted.aleo;\nfunction main:\n input r0 as u32.public;\n output r0 as u32.public;\n"; + let program: Program = source.parse().expect("The program must parse"); + let checksum = program.to_checksum().map(|byte| *byte); + let old_lock = serde_json::json!({"version":2,"network":[{ + "name":"trusted.aleo","network":"testnet","edition":2,"checksum":vec![0;32] + }]}); + std::fs::write(project.join("leo.lock"), old_lock.to_string()).expect("The project lock must be written"); + let override_lock = serde_json::json!({"version":2,"network":[{ + "name":"trusted.aleo","network":"testnet","edition":4,"checksum":checksum + }]}) + .to_string(); + let lock_path = root.join("trusted.lock"); + std::fs::write(&lock_path, &override_lock).expect("The override lock must be written"); + let home = root.join("cache"); + let cache = home.join("registry/testnet/trusted/4"); + std::fs::create_dir_all(&cache).expect("The cache directory must be created"); + std::fs::write(cache.join("trusted.aleo"), source).expect("The verified cache fixture must be written"); + create_session_if_not_set_then(|_| { + let package = Package::from_directory_with_network_lock( + &project, + &home, + false, + false, + false, + Some(NetworkName::TestnetV0), + Some("http://localhost:1"), + 0, + Some(&lock_path), + ) + .expect("The trusted override must resolve the cached import"); + assert!(package.compilation_units.iter().any(|unit| unit.edition == Some(4))); + }); + let pins = leo_package::Lock::read(&project).expect("The project lock must remain valid"); + assert_eq!( + pins.network_pin("trusted.aleo", NetworkName::TestnetV0, None) + .expect("The original project pin must remain") + .edition, + 2 + ); + assert_eq!(std::fs::read_to_string(lock_path).expect("The override must remain readable"), override_lock); + std::fs::remove_dir_all(root).expect("The temporary directory must be removed"); + } + + #[test] + fn remote_loader_requires_pins_for_transitive_imports() { + use crate::cli::{context::Context, load_pinned_programs_from_network}; + use snarkvm::prelude::{Program, ProgramID, TestnetV0}; + use std::{ + io::{Read, Write}, + net::TcpListener, + time::{Duration, Instant}, + }; + + let root_source = r"import imported.aleo; +program trusted.aleo; +function main: + input r0 as u32.public; + output r0 as u32.public; +"; + let import_source = + "program imported.aleo;\nfunction main:\n input r0 as u32.public;\n output r0 as u32.public;\n"; + for pin_import in [false, true] { + let root = temp_dir().join(format!("leo_remote_import_pins_{}_{pin_import}", std::process::id())); + std::fs::create_dir_all(&root).expect("The temporary directory must be created"); + let mut pins = Vec::new(); + for source in [root_source, import_source].into_iter().take(if pin_import { 2 } else { 1 }) { + let program: Program = source.parse().expect("The fixture must parse"); + pins.push(serde_json::json!({"name":program.id().to_string(), "network":"testnet", "edition":3, + "checksum":program.to_checksum().map(|byte| *byte)})); + } + let lock_path = root.join("trusted.lock"); + std::fs::write(&lock_path, serde_json::json!({"version":2,"network":pins}).to_string()) + .expect("The trusted pins must be written"); + let listener = TcpListener::bind("127.0.0.1:0").expect("The local server must bind"); + listener.set_nonblocking(true).expect("The listener must support a timeout"); + let endpoint = format!("http://{}", listener.local_addr().expect("The local address must exist")); + let server = std::thread::spawn(move || { + for (name, source) in [("trusted", root_source), ("imported", import_source)] + .into_iter() + .take(if pin_import { 2 } else { 1 }) + { + let start = Instant::now(); + let mut stream = loop { + match listener.accept() { + Ok((stream, _)) => break stream, + Err(error) + if error.kind() == std::io::ErrorKind::WouldBlock + && start.elapsed() < Duration::from_secs(10) => + { + std::thread::sleep(Duration::from_millis(5)); + } + Err(error) => panic!("Expected a program request: {error}"), + } + }; + stream.set_nonblocking(false).expect("The accepted stream must block"); + stream.set_read_timeout(Some(Duration::from_secs(10))).expect("The read timeout must be set"); + let mut request = [0; 4096]; + let count = stream.read(&mut request).expect("The request must arrive"); + assert!( + String::from_utf8_lossy(&request[..count]).contains(&format!("/testnet/program/{name}.aleo/3")) + ); + let body = serde_json::to_string(source).expect("The program must serialize"); + write!( + stream, + "HTTP/1.1 200 OK\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}", + body.len() + ) + .expect("The response must be written"); + } + }); + let mut context = Context::new(Some(root.clone()), Some(root.join("cache")), false, None) + .expect("The context must be valid"); + context.network_lock = Some(lock_path); + create_session_if_not_set_then(|_| { + let id: ProgramID = "trusted.aleo".parse().expect("The ID must parse"); + let result = load_pinned_programs_from_network(&context, id, NetworkName::TestnetV0, &endpoint, 0); + if pin_import { + let programs = result.expect("Both trusted programs must load"); + assert_eq!(programs.len(), 2); + assert_eq!(programs[0].0.id().to_string(), "imported.aleo"); + assert!(programs.iter().all(|(_, edition)| *edition == Some(3))); + } else { + let error = result.expect_err("An unpinned transitive import must fail"); + assert!(error.to_string().contains("trusted checksum pin"), "{error}"); + assert!(!root.join("cache/registry/testnet/imported").exists()); + } + }); + server.join().expect("The server must finish"); + std::fs::remove_dir_all(root).expect("The temporary directory must be removed"); + } + } + #[test] #[serial] - fn add_network_dependency_rejects_missing_program() { + fn add_network_dependency_rejects_missing_pin() { let temp_dir = temp_dir(); let project_directory = temp_dir.join("add_missing_network_dep"); if project_directory.exists() { @@ -356,6 +627,7 @@ mod tests { path: Some(project_directory.clone()), home: None, package: None, + network_lock: None, }; let add = CLI { @@ -376,12 +648,14 @@ mod tests { path: Some(project_directory.clone()), home: Some(temp_dir.join(".aleo_add_missing")), package: None, + network_lock: None, }; create_session_if_not_set_then(|_| { run_with_args(new).expect("Failed to execute `leo new`"); - assert!(run_with_args(add).is_err(), "`leo add` should reject an unverifiable network dependency"); + let error = run_with_args(add).expect_err("An unpinned network dependency must fail"); + assert!(error.to_string().contains("trusted checksum pin"), "{error}"); let manifest_path = project_directory.join(leo_package::MANIFEST_FILENAME); let manifest = leo_package::Manifest::read_from_file(&manifest_path).unwrap(); @@ -429,6 +703,7 @@ mod tests { path: Some(project_directory.clone()), home: Some(temp_dir.join(".aleo")), package: None, + network_lock: None, }; create_session_if_not_set_then(|_| { @@ -480,6 +755,7 @@ mod tests { path: Some(project_directory.clone()), home: None, package: None, + network_lock: None, }; create_session_if_not_set_then(|_| { @@ -526,6 +802,7 @@ mod tests { path: Some(project_directory.clone()), home: None, package: None, + network_lock: None, }; create_session_if_not_set_then(|_| { @@ -569,6 +846,7 @@ mod tests { path: Some(project_directory.clone()), home: None, package: None, + network_lock: None, }; create_session_if_not_set_then(|_| { @@ -600,6 +878,7 @@ mod tests { path: Some(lib_directory.clone()), home: None, package: None, + network_lock: None, }; create_session_if_not_set_then(|_| { @@ -646,6 +925,7 @@ mod tests { path: Some(ws_directory.clone()), home: None, package: None, + network_lock: None, }; create_session_if_not_set_then(|_| { @@ -805,6 +1085,7 @@ mod tests { path: Some(pkg_dir.clone()), home: None, package: None, + network_lock: None, }; create_session_if_not_set_then(|_| { @@ -847,6 +1128,7 @@ mod tests { path: Some(pkg_dir.clone()), home: None, package: None, + network_lock: None, }; create_session_if_not_set_then(|_| { @@ -886,6 +1168,7 @@ mod tests { path: Some(ws_root.clone()), home: None, package: None, + network_lock: None, }; create_session_if_not_set_then(|_| { @@ -922,6 +1205,7 @@ mod tests { path: Some(ws_root.join("token")), home: None, package: None, + network_lock: None, }; create_session_if_not_set_then(|_| { @@ -958,6 +1242,7 @@ mod tests { path: Some(ws_root.clone()), home: None, package: Some("token".to_string()), + network_lock: None, }; create_session_if_not_set_then(|_| { @@ -991,6 +1276,7 @@ mod tests { path: Some(ws_root.clone()), home: None, package: Some("nonexistent".to_string()), + network_lock: None, }; create_session_if_not_set_then(|_| { @@ -1026,6 +1312,7 @@ mod tests { path: Some(ws_root.clone()), home: None, package: None, + network_lock: None, }; create_session_if_not_set_then(|_| { @@ -1045,6 +1332,7 @@ mod tests { path: Some(ws_root.clone()), home: None, package: None, + network_lock: None, }; create_session_if_not_set_then(|_| { @@ -1080,6 +1368,7 @@ mod tests { path: Some(ws_root.clone()), home: None, package: None, + network_lock: None, }; create_session_if_not_set_then(|_| { @@ -1117,6 +1406,7 @@ mod tests { path: Some(ws_root.join("swap")), home: None, package: None, + network_lock: None, }; create_session_if_not_set_then(|_| { @@ -1153,6 +1443,7 @@ mod tests { path: Some(ws_root.join("swap")), home: None, package: None, + network_lock: None, }; create_session_if_not_set_then(|_| { @@ -1205,6 +1496,7 @@ mod tests { path: Some(ws_root.clone()), home: None, package: None, + network_lock: None, }; create_session_if_not_set_then(|_| { @@ -1286,6 +1578,7 @@ mod tests { home: None, // Filter to just token. package: Some("token".to_string()), + network_lock: None, }; create_session_if_not_set_then(|_| { @@ -1339,6 +1632,7 @@ mod tests { path: Some(ws_root.clone()), home: None, package: None, + network_lock: None, }; create_session_if_not_set_then(|_| { @@ -1394,6 +1688,7 @@ mod tests { path: Some(ws_root.clone()), home: None, package: None, + network_lock: None, }; create_session_if_not_set_then(|_| { @@ -1431,6 +1726,7 @@ mod tests { path: Some(pkg_dir.clone()), home: None, package: None, + network_lock: None, }; create_session_if_not_set_then(|_| { @@ -1455,6 +1751,7 @@ mod tests { path: Some(pkg_dir.clone()), home: None, package: None, + network_lock: None, }; create_session_if_not_set_then(|_| { @@ -1945,6 +2242,7 @@ program app.aleo { path: Some(project_directory.clone()), home: None, package: None, + network_lock: None, }; create_session_if_not_set_then(|_| { @@ -2022,6 +2320,23 @@ function external_nested_function: std::fs::create_dir_all(&dir).unwrap(); std::fs::write(dir.join("nested_example_layer_2.aleo"), nested_example_layer_2).unwrap(); + // Trust the fixture programs independently of the cache. + let pins = [nested_example_layer_0, nested_example_layer_1, nested_example_layer_2].map(|source| { + let program: snarkvm::prelude::Program = + source.parse().expect("The fixture program must parse"); + serde_json::json!({ + "name": program.id().to_string(), + "network": "testnet", + "edition": 0, + "checksum": program.to_checksum().map(|byte| *byte), + }) + }); + std::fs::write( + project_directory.join("leo.lock"), + serde_json::json!({"version": 2, "network": pins}).to_string(), + ) + .expect("The fixture pins must be written"); + // Add dependencies let add = CLI { debug: false, @@ -2047,6 +2362,7 @@ function external_nested_function: path: Some(project_directory.clone()), home: Some(temp_dir.join(".aleo")), package: None, + network_lock: None, }; create_session_if_not_set_then(|_| { @@ -2075,6 +2391,7 @@ function external_nested_function: path: Some(grandparent_directory.clone()), home: None, package: None, + network_lock: None, }; let create_parent_project = CLI { @@ -2086,6 +2403,7 @@ function external_nested_function: path: Some(parent_directory.clone()), home: None, package: None, + network_lock: None, }; let create_child_project = CLI { @@ -2097,6 +2415,7 @@ function external_nested_function: path: Some(child_directory.clone()), home: None, package: None, + network_lock: None, }; // Add source files `grandparent/src/main.leo`, `grandparent/parent/src/main.leo`, and `grandparent/parent/child/src/main.leo` @@ -2165,6 +2484,7 @@ program child.aleo { path: Some(grandparent_directory.clone()), home: None, package: None, + network_lock: None, }; let add_grandparent_dependency_2 = CLI { @@ -2191,6 +2511,7 @@ program child.aleo { path: Some(grandparent_directory.clone()), home: None, package: None, + network_lock: None, }; let add_parent_dependency = CLI { @@ -2217,6 +2538,7 @@ program child.aleo { path: Some(parent_directory.clone()), home: None, package: None, + network_lock: None, }; // Execute all commands @@ -2257,6 +2579,7 @@ program child.aleo { path: Some(outer_directory.clone()), home: None, package: None, + network_lock: None, }; let create_inner_1_project = CLI { @@ -2270,6 +2593,7 @@ program child.aleo { path: Some(inner_1_directory.clone()), home: None, package: None, + network_lock: None, }; let create_inner_2_project = CLI { @@ -2283,6 +2607,7 @@ program child.aleo { path: Some(inner_2_directory.clone()), home: None, package: None, + network_lock: None, }; // Add source files `outer/src/main.leo` and `outer/inner/src/main.leo` @@ -2374,6 +2699,7 @@ program inner_2.aleo { path: Some(outer_directory.clone()), home: None, package: None, + network_lock: None, }; let add_outer_dependency_2 = CLI { @@ -2400,6 +2726,7 @@ program inner_2.aleo { path: Some(outer_directory.clone()), home: None, package: None, + network_lock: None, }; // Execute all commands @@ -2441,6 +2768,7 @@ program inner_2.aleo { path: Some(outer_directory.clone()), home: None, package: None, + network_lock: None, }; let create_inner_1_project = CLI { @@ -2454,6 +2782,7 @@ program inner_2.aleo { path: Some(inner_1_directory.clone()), home: None, package: None, + network_lock: None, }; let create_inner_2_project = CLI { @@ -2467,6 +2796,7 @@ program inner_2.aleo { path: Some(inner_2_directory.clone()), home: None, package: None, + network_lock: None, }; // Add source files `outer_2/src/main.leo` and `outer_2/inner/src/main.leo` @@ -2592,6 +2922,7 @@ program inner_2.aleo { path: Some(outer_directory.clone()), home: None, package: None, + network_lock: None, }; let add_outer_dependency_2 = CLI { @@ -2618,6 +2949,7 @@ program inner_2.aleo { path: Some(outer_directory.clone()), home: None, package: None, + network_lock: None, }; // Execute all commands diff --git a/crates/leo/src/cli/commands/add.rs b/crates/leo/src/cli/commands/add.rs index 4ea4921ddf6..45bc0a25b4c 100755 --- a/crates/leo/src/cli/commands/add.rs +++ b/crates/leo/src/cli/commands/add.rs @@ -222,7 +222,7 @@ impl Command for LeoAdd { // Pin the resolved commit so the next build reuses this checkout instead of re-fetching. let lock_dir = Workspace::discover_root(&path)?.unwrap_or_else(|| path.clone()); - let mut lock = Lock::read(&lock_dir); + let mut lock = Lock::read(&lock_dir)?; lock.record(name.clone(), url.clone(), reference.lock_string(), commit); lock.write(&lock_dir)?; @@ -257,6 +257,7 @@ impl Command for LeoAdd { &endpoint, false, self.network_retries, + &context.network_pins()?, ) .map_err(|err| { crate::errors::custom(format!( diff --git a/crates/leo/src/cli/commands/build.rs b/crates/leo/src/cli/commands/build.rs index f9de4eb9285..d8dc7a2f19e 100644 --- a/crates/leo/src/cli/commands/build.rs +++ b/crates/leo/src/cli/commands/build.rs @@ -127,7 +127,7 @@ fn handle_build(command: &LeoBuild, context: Context) -> Result< Result< Result<( }, }, } - .execute(Context::new(context.path.clone(), context.home.clone(), true, None)?)?; + .execute(Context { recursive: true, package_filter: None, ..context.clone() })?; // Remove the last 3 characters since they represent the `u64` suffix. public_balance.truncate(public_balance.len() - 3); // Make sure the balance is valid. @@ -76,7 +76,7 @@ pub fn get_latest_block_height( }, }, } - .execute(Context::new(context.path.clone(), context.home.clone(), true, None)?)?; + .execute(Context { recursive: true, package_filter: None, ..context.clone() })?; // Parse the height. let height = height.parse::().map_err(crate::errors::string_parse_error)?; Ok(height) @@ -137,8 +137,8 @@ pub fn handle_broadcast( } } -/// Loads the latest edition of a program and all its imports from the network, using an iterative DFS. -pub fn load_latest_programs_from_network( +/// Load the trusted editions of a program and its imports with an iterative DFS. +pub fn load_pinned_programs_from_network( context: &Context, program_id: ProgramID, network: NetworkName, @@ -148,6 +148,8 @@ pub fn load_latest_programs_from_network( use snarkvm::prelude::Program; use std::collections::HashSet; + let network_pins = context.network_pins()?; + // A cache for loaded programs, mapping a program ID to its bytecode and edition. let mut programs = HashMap::new(); // The ordered set of programs. @@ -177,8 +179,8 @@ pub fn load_latest_programs_from_network( endpoint, true, network_retries, - ) - .map_err(|_| crate::errors::custom(format!("Failed to fetch program source for ID: {current_id}")))?; + &network_pins, + )?; let ProgramData::Bytecode(program_src) = program.data else { panic!("Expected bytecode when fetching a remote program"); }; diff --git a/crates/leo/src/cli/commands/common/util.rs b/crates/leo/src/cli/commands/common/util.rs index e8d6d4ece78..fc69a3ccc1d 100644 --- a/crates/leo/src/cli/commands/common/util.rs +++ b/crates/leo/src/cli/commands/common/util.rs @@ -133,7 +133,7 @@ pub fn load_extra_programs_into_vm( println!("⬇️ Fetching remote program {name} and its dependencies from {endpoint}..."); let program_id = ProgramID::::from_str(&name) .map_err(|e| crate::errors::custom(format!("Failed to parse program ID '{name}': {e}")))?; - let fetched = super::query::load_latest_programs_from_network( + let fetched = super::query::load_pinned_programs_from_network( context, program_id, network, @@ -155,6 +155,37 @@ mod tests { use snarkvm::prelude::TestnetV0; use std::str::FromStr; + #[test] + fn extra_remote_program_requires_a_trusted_pin() { + use crate::cli::context::Context; + use snarkvm::prelude::{ + VM, + store::{ConsensusStore, helpers::memory::ConsensusMemory}, + }; + + let root = std::env::temp_dir().join(format!("leo_extra_pin_{}", std::process::id())); + std::fs::create_dir_all(&root).expect("The temporary directory must be created"); + let context = + Context::new(Some(root.clone()), Some(root.join("cache")), false, None).expect("The context must be valid"); + let store = ConsensusStore::>::open(aleo_std::StorageMode::Production) + .expect("The memory store must open"); + let vm = VM::from(store).expect("The VM must initialize"); + leo_span::create_session_if_not_set_then(|_| { + let error = load_extra_programs_into_vm( + &["unpinned.aleo".to_string()], + &vm, + &context, + leo_ast::NetworkName::TestnetV0, + Some("http://localhost:1"), + 0, + ) + .expect_err("An unpinned extra program must fail"); + assert!(error.to_string().contains("trusted checksum pin"), "{error}"); + }); + assert!(!root.join("cache").exists()); + std::fs::remove_dir_all(root).expect("The temporary directory must be removed"); + } + #[test] fn test_edition_constructor_error_message() { // A program without a constructor at edition 0 should fail under V8+ diff --git a/crates/leo/src/cli/commands/deploy.rs b/crates/leo/src/cli/commands/deploy.rs index 87e75d0ca45..18e9483d974 100644 --- a/crates/leo/src/cli/commands/deploy.rs +++ b/crates/leo/src/cli/commands/deploy.rs @@ -148,7 +148,7 @@ impl Command for LeoDeploy { let home_path = context.home()?; let network = get_network(&self.env_override.network)?; let endpoint = get_endpoint(&self.env_override.endpoint)?; - return Package::from_aleo_file( + return Package::from_aleo_file_with_network_lock( path, home_path, self.imports_dir.as_deref(), @@ -157,6 +157,7 @@ impl Command for LeoDeploy { Some(network), Some(&endpoint), self.env_override.network_retries, + context.network_lock.as_deref(), ); } if self.imports_dir.is_some() { diff --git a/crates/leo/src/cli/commands/execute.rs b/crates/leo/src/cli/commands/execute.rs index 204eaeb31ed..ebaf16ced88 100644 --- a/crates/leo/src/cli/commands/execute.rs +++ b/crates/leo/src/cli/commands/execute.rs @@ -123,7 +123,7 @@ impl Command for LeoExecute { if context.package_filter.is_some() { return Err(crate::errors::custom("`--package` cannot be used with an Aleo bytecode file.").into()); } - return Package::from_aleo_file( + return Package::from_aleo_file_with_network_lock( path, home_path, self.imports_dir.as_deref(), @@ -132,6 +132,7 @@ impl Command for LeoExecute { Some(network), Some(&endpoint), self.env_override.network_retries, + context.network_lock.as_deref(), ) .map(Some); } @@ -417,7 +418,7 @@ fn handle_execute( // Note: The dependencies are downloaded in "post-order" (child before parent). if !is_local { println!("⬇️ Downloading {program_name} and its dependencies from {endpoint}..."); - programs = load_latest_programs_from_network( + programs = load_pinned_programs_from_network( &context, program_id, network, diff --git a/crates/leo/src/cli/commands/remove.rs b/crates/leo/src/cli/commands/remove.rs index 6db79a480b5..1b22d92e53b 100644 --- a/crates/leo/src/cli/commands/remove.rs +++ b/crates/leo/src/cli/commands/remove.rs @@ -123,7 +123,7 @@ impl Command for LeoRemove { // Prune the removed git dependencies' pins so the lock doesn't accumulate dead entries. if !removed_git_names.is_empty() { let lock_dir = Workspace::discover_root(&path)?.unwrap_or_else(|| path.clone()); - let mut lock = Lock::read(&lock_dir); + let mut lock = Lock::read(&lock_dir)?; for name in &removed_git_names { lock.remove_name(name); } diff --git a/crates/leo/src/cli/commands/run.rs b/crates/leo/src/cli/commands/run.rs index 6b4f7047387..2699041e2a3 100644 --- a/crates/leo/src/cli/commands/run.rs +++ b/crates/leo/src/cli/commands/run.rs @@ -104,7 +104,7 @@ impl Command for LeoRun { DEFAULT_ENDPOINT.to_string() } }; - return Package::from_aleo_file( + return Package::from_aleo_file_with_network_lock( path, home_path, self.imports_dir.as_deref(), @@ -113,6 +113,7 @@ impl Command for LeoRun { Some(network), Some(&endpoint), self.env_override.network_retries, + context.network_lock.as_deref(), ) .map(Some); } @@ -314,7 +315,7 @@ fn handle_run( let endpoint = get_endpoint(&command.env_override.endpoint)?; println!("⬇️ Downloading {program_name} and its dependencies from {endpoint}..."); // Load the programs from the network. - programs = load_latest_programs_from_network( + programs = load_pinned_programs_from_network( &context, program_id, network, diff --git a/crates/leo/src/cli/commands/synthesize.rs b/crates/leo/src/cli/commands/synthesize.rs index f52fe97a08e..c16b4e8435d 100644 --- a/crates/leo/src/cli/commands/synthesize.rs +++ b/crates/leo/src/cli/commands/synthesize.rs @@ -173,7 +173,7 @@ fn handle_synthesize( // Note: The dependencies are downloaded in "post-order" (child before parent). if !is_local { println!("⬇️ Downloading {program_id} and its dependencies from {endpoint}..."); - programs = load_latest_programs_from_network( + programs = load_pinned_programs_from_network( &context, program_id, network, diff --git a/crates/leo/src/cli/commands/upgrade.rs b/crates/leo/src/cli/commands/upgrade.rs index 68318b8c1a1..086758dc7a4 100644 --- a/crates/leo/src/cli/commands/upgrade.rs +++ b/crates/leo/src/cli/commands/upgrade.rs @@ -18,7 +18,7 @@ use super::*; use std::{collections::HashSet, fs}; use leo_ast::NetworkName; -use leo_package::{Package, ProgramData, fetch_program_from_network}; +use leo_package::{Package, ProgramData}; #[cfg(not(feature = "only_testnet"))] use snarkvm::prelude::{CanaryV0, MainnetV0}; @@ -40,7 +40,6 @@ use crate::cli::{ use aleo_std::StorageMode; use colored::*; use itertools::Itertools; -use leo_span::Symbol; use snarkvm::{ prelude::{ConsensusVersion, ProgramID, Stack, check_program_plaintext_sizes, store::helpers::memory::BlockMemory}, synthesizer::program::StackTrait, @@ -93,7 +92,7 @@ impl Command for LeoUpgrade { let home_path = context.home()?; let network = get_network(&self.env_override.network)?; let endpoint = get_endpoint(&self.env_override.endpoint)?; - return Package::from_aleo_file( + return Package::from_aleo_file_with_network_lock( path, home_path, self.imports_dir.as_deref(), @@ -102,6 +101,7 @@ impl Command for LeoUpgrade { Some(network), Some(&endpoint), self.env_override.network_retries, + context.network_lock.as_deref(), ); } if self.imports_dir.is_some() { @@ -265,12 +265,25 @@ fn handle_upgrade>( command.env_override.network_retries, )?; - // Fail closed for proven-invalid upgrades by using the stricter active endpoint rules; fetch uncertainty remains a warning below. + // Load trusted old programs and their imports before checking or authorizing the upgrade. + let mut programs_and_editions = Vec::with_capacity(program_ids.len()); + let mut loaded = HashSet::new(); + for id in &program_ids { + for (program, edition) in + load_pinned_programs_from_network(&context, *id, network, &endpoint, command.env_override.network_retries)? + { + if loaded.insert(*program.id()) { + programs_and_editions.push((program, edition.unwrap_or(LOCAL_PROGRAM_DEFAULT_EDITION))); + } + } + } + + // Use the stricter active endpoint rules to check the upgrade. let validation_consensus_version = get_endpoint_consensus_version(&endpoint, network, command.env_override.network_retries) .map_or(consensus_version, |network_version| consensus_version.max(network_version)); let remote_programs = - validate_upgrade_tasks(&endpoint, network, &local, &skipped, validation_consensus_version, command)?; + validate_upgrade_tasks(&programs_and_editions, &local, &skipped, validation_consensus_version)?; // Build the config for JSON output. let config = Some(Config { @@ -306,33 +319,6 @@ fn handle_upgrade>( // Initialize a new VM. let vm = VM::from(ConsensusStore::>::open(StorageMode::Production)?)?; - // Load all the programs from the network into the VM. - let mut programs_and_editions = Vec::with_capacity(program_ids.len()); - for id in &program_ids { - // Load the program from the network. - let Ok(program) = leo_package::CompilationUnit::fetch( - Symbol::intern(&id.name().to_string()), - None, - context.home()?, - network, - &endpoint, - true, - command.env_override.network_retries, - ) else { - warn_and_confirm(&format!("Failed to fetch program {id} from the network."), command.extra.yes)?; - continue; - }; - let ProgramData::Bytecode(bytecode) = program.data else { - panic!("Expected bytecode when fetching a remote program"); - }; - // Parse the program bytecode. - let bytecode = Program::::from_str(&bytecode) - .map_err(|e| crate::errors::custom(format!("Failed to parse program: {e}")))?; - // Program::fetch should always set the edition after a successful fetch. - let edition = program.edition.expect("Edition should be set after successful fetch"); - programs_and_editions.push((bytecode, edition)); - } - // Check for programs that violate edition/constructor requirements. check_edition_constructor_requirements(&programs_and_editions, consensus_version, "upgrade")?; @@ -526,12 +512,10 @@ fn handle_upgrade>( } fn validate_upgrade_tasks( - endpoint: &str, - network: NetworkName, + verified_programs: &[(Program, u16)], tasks: &[Task], skipped: &HashSet>, consensus_version: ConsensusVersion, - command: &LeoUpgrade, ) -> Result, Program)>> { let mut remote_programs = Vec::with_capacity(tasks.len()); @@ -541,17 +525,14 @@ fn validate_upgrade_tasks( continue; } - let Ok(remote_program) = - fetch_program_from_network(&id.to_string(), endpoint, network, command.env_override.network_retries) - else { - // Fetch uncertainty remains in `check_tasks_for_warnings` so transient network state does not block planning. - continue; - }; - let Ok(remote_program) = Program::::from_str(&remote_program) else { - continue; - }; - reject_invalid_upgrade(id, &remote_program, program, consensus_version)?; - remote_programs.push((*id, remote_program)); + let remote_program = + verified_programs + .iter() + .find(|(remote, _)| remote.id() == id) + .map(|(remote, _)| remote) + .ok_or_else(|| crate::errors::custom(format!("Missing verified program for upgrade: {id}")))?; + reject_invalid_upgrade(id, remote_program, program, consensus_version)?; + remote_programs.push((*id, remote_program.clone())); } Ok(remote_programs) @@ -621,20 +602,6 @@ fn check_tasks_for_warnings( // Check if the program exists on the network. if let Some((_, remote_program)) = remote_programs.iter().find(|(remote_id, _)| remote_id == id) { push_remote_upgrade_warnings(id, remote_program, program, consensus_version, &mut warnings); - } else if let Ok(remote_program) = - fetch_program_from_network(&id.to_string(), endpoint, network, command.env_override.network_retries) - { - // Parse the program. - let remote_program = match Program::::from_str(&remote_program) { - Ok(program) => program, - Err(e) => { - warnings.push(format!("Could not parse '{id}' from the network. Error: {e}",)); - continue; - } - }; - push_remote_upgrade_warnings(id, &remote_program, program, consensus_version, &mut warnings); - } else { - warnings.push(format!("The program '{id}' does not exist on the network. The upgrade will likely fail.",)); } // Check if the program has a valid naming scheme. if consensus_version >= ConsensusVersion::V7 diff --git a/crates/leo/src/cli/helpers/context.rs b/crates/leo/src/cli/helpers/context.rs index f0bc2b90993..3f86a8d7943 100644 --- a/crates/leo/src/cli/helpers/context.rs +++ b/crates/leo/src/cli/helpers/context.rs @@ -16,7 +16,7 @@ use aleo_std; use leo_errors::Result; -use leo_package::{Manifest, Workspace}; +use leo_package::{Lock, Manifest, Workspace}; use aleo_std::aleo_dir; use std::{env::current_dir, path::PathBuf}; @@ -30,6 +30,8 @@ pub struct Context { pub path: Option, /// Path to use for the Aleo registry, None when default pub home: Option, + /// Optional file with trusted network program pins. + pub network_lock: Option, /// Recursive flag. // TODO: Shift from callee to caller by including display method pub recursive: bool, @@ -44,7 +46,7 @@ impl Context { recursive: bool, package_filter: Option, ) -> Result { - Ok(Context { path, home, recursive, package_filter }) + Ok(Context { path, home, network_lock: None, recursive, package_filter }) } /// Returns the path of the parent directory to the Leo package. @@ -75,6 +77,20 @@ impl Context { } } + /// Read trusted pins from the explicit file or the package lock. + pub fn network_pins(&self) -> Result { + if let Some(path) = &self.network_lock { + return Lock::read_file(path); + } + let path = self.dir()?; + if path.extension().and_then(|extension| extension.to_str()) == Some("aleo") { + let path = path.canonicalize().map_err(crate::errors::cli_io_error)?; + return Lock::read(path.parent().unwrap_or_else(|| std::path::Path::new("."))); + } + let root = Workspace::discover_root(&path)?.unwrap_or(path); + Lock::read(&root) + } + /// Opens the manifest file `program.json`. pub fn open_manifest(&self) -> Result { let path = self.dir()?; @@ -131,6 +147,12 @@ impl Context { /// Create a new `Context` pointing at a specific directory. pub fn with_path(&self, path: PathBuf) -> Self { - Context { path: Some(path), home: self.home.clone(), recursive: self.recursive, package_filter: None } + Context { + path: Some(path), + home: self.home.clone(), + network_lock: self.network_lock.clone(), + recursive: self.recursive, + package_filter: None, + } } } diff --git a/crates/package/README.md b/crates/package/README.md index 7a139eb435e..5be95dc56e6 100644 --- a/crates/package/README.md +++ b/crates/package/README.md @@ -3,3 +3,40 @@ [![Crates.io](https://img.shields.io/crates/v/leo-package.svg?color=neon)](https://crates.io/crates/leo-package) [![Authors](https://img.shields.io/badge/authors-Aleo-orange.svg)](../AUTHORS) [![License](https://img.shields.io/badge/License-GPLv3-blue.svg)](./LICENSE.md) + +## Trusted network programs + +Network programs require trusted checksum pins in `leo.lock`. The `program.json` format does not change. A pin specifies a program ID, network, edition, and checksum. Leo checks the pin before it accepts cached or downloaded bytecode. + +The lock format is JSON. Version 2 adds a `network` array to the existing `git` array. Each network entry has these fields: + +| Field | Value | +| --- | --- | +| `name` | The full program ID, for example `token.aleo`. | +| `network` | `mainnet`, `testnet`, or `canary`. | +| `edition` | The trusted edition number, from 0 to 65535. | +| `checksum` | An array of 32 integers, each from 0 to 255. | + +Use one entry for each program and network. Include the remote target and every transitive import. The checksum is the SHA3-256 hash of the canonical program text from snarkVM `Program::to_checksum()`. A hash of the raw file can differ because of comments or spacing. + +To obtain a checksum from reviewed Leo source, run this command in that source package: + +```sh +leo build --checksums --json-output=checksums.json +``` + +Copy the `program_checksum` byte array from the JSON output into the pin's `checksum` field. Obtain the program ID, network, and edition from an independent trusted source. For existing Aleo bytecode, use the snarkVM `Program::to_checksum()` method after you review the code. Do not create a trusted pin from an unchecked endpoint response or an old cache entry. + +Leo reads `leo.lock` from the workspace root, or from the package directory when there is no workspace. For a standalone `.aleo` file, Leo reads the lock beside that file. To use a separate trust file, pass its path: + +```sh +leo execute token.aleo/transfer --network-lock /path/to/trusted.lock +``` + +The global `--network-lock` option also applies to builds, remote `--with` programs, and upgrades. Leo does not write to a separate trust file. Git resolution still updates the package or workspace lock. Use a trust file that you control; a dependency's own lock does not supply trusted pins. + +An omitted manifest edition uses the pinned edition. An explicit edition must match the pin. Leo does not select the highest cached edition, request the latest edition, or fall back to an unversioned program URL. To approve an upgrade, replace the pin with the independently verified edition and checksum. For `leo upgrade`, pin the old deployed program and its imports so Leo can check the upgrade against trusted code. + +Missing pins, checksum mismatches, duplicate pins, and invalid lock files stop the operation. Neither `--yes` nor `--no-cache` bypasses verification. A valid version 1 Git lock remains readable, but network dependencies require pins. Builds preserve network pins, including pins not used by the current package. The built-in `credits.aleo` program comes from snarkVM at edition zero and needs no external pin. + +Pins authenticate program content. They do not authenticate other node responses, such as balances or block heights. diff --git a/crates/package/src/compilation_unit.rs b/crates/package/src/compilation_unit.rs index fa54105fabc..cfb163ff19a 100644 --- a/crates/package/src/compilation_unit.rs +++ b/crates/package/src/compilation_unit.rs @@ -19,31 +19,11 @@ use crate::{MAX_PROGRAM_SIZE, *}; use leo_errors::Result; use leo_span::Symbol; -use snarkvm::prelude::{Program as SvmProgram, TestnetV0}; +use snarkvm::prelude::{CanaryV0, MainnetV0, Program as SvmProgram, ProgramID, TestnetV0}; use indexmap::{IndexMap, IndexSet}; use std::path::Path; -/// Find the latest cached edition for a program in the local registry. -/// Returns None if no cached version exists. -fn find_cached_edition(cache_directory: &Path, name: &str) -> Option { - let program_cache = cache_directory.join(name); - if !program_cache.exists() { - return None; - } - - // List edition directories and find the highest one - std::fs::read_dir(&program_cache) - .ok()? - .filter_map(|entry| entry.ok()) - .filter_map(|entry| { - let file_name = entry.file_name(); - let name = file_name.to_str()?; - name.parse::().ok() - }) - .max() -} - /// The kind of a Leo compilation unit: a deployable program, a library, or a test. #[derive(Clone, Debug, PartialEq, Eq)] pub enum PackageKind { @@ -225,8 +205,8 @@ impl CompilationUnit { }) } - /// Given an Aleo program on a network, fetch it to build a `CompilationUnit`. - /// If no edition is found, the latest edition is pulled from the network. + /// Fetch a program at its trusted pinned edition and verify its bytecode. + #[allow(clippy::too_many_arguments)] pub fn fetch>( name: Symbol, edition: Option, @@ -235,10 +215,12 @@ impl CompilationUnit { endpoint: &str, no_cache: bool, network_retries: u32, + pins: &Lock, ) -> Result { - Self::fetch_impl(name, edition, home_path.as_ref(), network, endpoint, no_cache, network_retries) + Self::fetch_impl(name, edition, home_path.as_ref(), network, endpoint, no_cache, network_retries, pins) } + #[allow(clippy::too_many_arguments)] fn fetch_impl( name: Symbol, edition: Option, @@ -247,106 +229,74 @@ impl CompilationUnit { endpoint: &str, no_cache: bool, network_retries: u32, + pins: &Lock, ) -> Result { - // Callers may pass the name with or without the ".aleo" suffix; normalise to bare name - // here so cache paths and network URLs are constructed consistently. - let name = Symbol::intern(name.to_string().strip_suffix(".aleo").unwrap_or(&name.to_string())); - - // It's not a local program; let's check the cache. - let cache_directory = home_path.join(format!("registry/{network}")); - - // If the edition is not specified, try to find a cached version first, - // then fall back to querying the network for the latest edition. - let edition = match edition { - // Credits program always has edition 0. - _ if name == Symbol::intern("credits") => 0, - Some(edition) => edition, - None if !no_cache => { - // Check if we have a cached version - avoid network call if possible. - match find_cached_edition(&cache_directory, &name.to_string()) { - Some(cached_edition) => cached_edition, - None => crate::fetch_latest_edition(&name.to_string(), endpoint, network, network_retries)?, - } + // Validate the name before using it in cache paths or network URLs. + let program_id = canonicalize_program_name(&name.to_string()) + .parse::>() + .map_err(|_| crate::errors::cli_invalid_package_name("program", name))?; + let full_name = program_id.to_string(); + let name = Symbol::intern(&program_id.name().to_string()); + + // The native program is trusted through snarkVM, not the endpoint or cache. + if full_name == "credits.aleo" { + if edition.is_some_and(|edition| edition != 0) { + return Err( + crate::errors::untrusted_network_program(&full_name, "credits must use edition zero").into() + ); } - // no_cache is set - user wants fresh data from network. - None => crate::fetch_latest_edition(&name.to_string(), endpoint, network, network_retries)?, - }; + let bytecode = match network { + NetworkName::MainnetV0 => SvmProgram::::credits()?.to_string(), + NetworkName::TestnetV0 => SvmProgram::::credits()?.to_string(), + NetworkName::CanaryV0 => SvmProgram::::credits()?.to_string(), + }; + return Ok(Self { + name: Symbol::intern(&full_name), + edition: Some(0), + dependencies: parse_dependencies_from_aleo(name, &bytecode, &IndexMap::new())?, + data: ProgramData::Bytecode(bytecode), + is_local: false, + kind: PackageKind::Program, + }); + } - // Define the full cache path for the program. + let pin = pins.network_pin(&full_name, network, edition)?; + let edition = pin.edition; + let cache_directory = home_path.join(format!("registry/{network}/{name}/{edition}")); + let full_cache_path = cache_directory.join(&full_name); + let use_cache = !no_cache && full_cache_path.exists(); + let bytecode = if use_cache { + std::fs::read_to_string(&full_cache_path).map_err(|err| { + crate::errors::util_file_io_error( + format_args!("Trying to read cached file at {}", full_cache_path.display()), + err, + ) + })? + } else { + // An unversioned fallback cannot establish the requested edition. + let url = format!("{endpoint}/{network}/program/{full_name}/{edition}"); + fetch_from_network(&url, network_retries)? + }; + pin.verify(&bytecode)?; + let dependencies = parse_dependencies_from_aleo(name, &bytecode, &IndexMap::new())?; - // Build cache paths. - let cache_directory = cache_directory.join(format!("{name}/{edition}")); - let full_cache_path = cache_directory.join(format!("{name}.aleo")); - if !cache_directory.exists() { - // Create directory if it doesn't exist. + if !use_cache { std::fs::create_dir_all(&cache_directory).map_err(|err| { - crate::errors::util_file_io_error(format!("Could not write path {}", cache_directory.display()), err) + crate::errors::util_file_io_error( + format_args!("Could not write path {}", cache_directory.display()), + err, + ) + })?; + std::fs::write(&full_cache_path, &bytecode).map_err(|err| { + crate::errors::util_file_io_error( + format_args!("Could not write file {}", full_cache_path.display()), + err, + ) })?; } - // Get the existing bytecode if the file exists. - let existing_bytecode = match full_cache_path.exists() { - false => None, - true => { - let existing_contents = std::fs::read_to_string(&full_cache_path).map_err(|e| { - crate::errors::util_file_io_error( - format_args!("Trying to read cached file at {}", full_cache_path.display()), - e, - ) - })?; - Some(existing_contents) - } - }; - - let bytecode = match (existing_bytecode, no_cache) { - // If we are using the cache, we can just return the bytecode. - (Some(bytecode), false) => bytecode, - // Otherwise, we need to fetch it from the network. - (existing, _) => { - // Define the primary URL to fetch the program from. - let primary_url = if name == Symbol::intern("credits") { - format!("{endpoint}/{network}/program/credits.aleo") - } else { - format!("{endpoint}/{network}/program/{name}.aleo/{edition}") - }; - let secondary_url = format!("{endpoint}/{network}/program/{name}.aleo"); - let contents = fetch_from_network(&primary_url, network_retries) - .or_else(|_| fetch_from_network(&secondary_url, network_retries)) - .map_err(|err| { - crate::errors::failed_to_retrieve_from_endpoint( - primary_url, - format_args!("Failed to fetch program `{name}` from network `{network}`: {err}"), - ) - })?; - - // If the file already exists, compare it to the new contents. - if let Some(existing_contents) = existing - && existing_contents != contents - { - println!( - "Warning: The cached file at `{}` is different from the one fetched from the network. The cached file will be overwritten.", - full_cache_path.display() - ); - } - - // Write the bytecode to the cache. - std::fs::write(&full_cache_path, &contents).map_err(|err| { - crate::errors::util_file_io_error( - format_args!("Could not open file `{}`", full_cache_path.display()), - err, - ) - })?; - - contents - } - }; - - let dependencies = parse_dependencies_from_aleo(name, &bytecode, &IndexMap::new())?; - - Ok(CompilationUnit { - // Network programs store the name with the ".aleo" suffix (unlike local packages). - // TODO: unify the invariant so the suffix is always absent. - name: Symbol::intern(&(name.to_string() + ".aleo")), + Ok(Self { + name: Symbol::intern(&full_name), data: ProgramData::Bytecode(bytecode), edition: Some(edition), dependencies, diff --git a/crates/package/src/errors.rs b/crates/package/src/errors.rs index 82652fa6959..71bbf5e0ed5 100644 --- a/crates/package/src/errors.rs +++ b/crates/package/src/errors.rs @@ -24,6 +24,16 @@ use std::{ const CODE_PREFIX: &str = "PAK"; const CODE_MASK: i32 = 5000; +pub(crate) fn invalid_lock_file(path: impl Display, reason: impl Display) -> Backtraced { + Backtraced::error(CODE_PREFIX, CODE_MASK + 84, format!("invalid lock file at `{path}`: {reason}")) + .with_help("Restore a valid lock file. Do not replace trusted pins with values from an untrusted endpoint.") +} + +pub(crate) fn untrusted_network_program(name: impl Display, reason: impl Display) -> Backtraced { + Backtraced::error(CODE_PREFIX, CODE_MASK + 85, format!("cannot trust network program `{name}`: {reason}")) + .with_help("Add the program's network, edition, and checksum from reviewed bytecode or an independent trusted source to `leo.lock`. Use `--network-lock ` to select a trusted lock file.") +} + pub(crate) fn io_error_gitignore_file(error: impl ErrorArg) -> Backtraced { Backtraced::error(CODE_PREFIX, CODE_MASK + 16, format!("failed to write `.gitignore`: {error}")) .with_help("Verify the package directory is writable.") diff --git a/crates/package/src/lock.rs b/crates/package/src/lock.rs index d027fb364cb..16de43c33ee 100644 --- a/crates/package/src/lock.rs +++ b/crates/package/src/lock.rs @@ -14,20 +14,20 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -//! The `leo.lock` lock file, which pins git dependencies to exact commits. -//! -//! Only git dependencies need locking (network deps are pinned by edition, path deps by location). -//! Entries are keyed by `(name, git, reference)`, so changing the requested reference re-resolves. +//! The `leo.lock` file pins git commits and trusted network program checksums. +use leo_ast::NetworkName; use leo_errors::Result; +use snarkvm::prelude::{Program, ProgramID, TestnetV0}; +use indexmap::IndexSet; use serde::{Deserialize, Serialize}; use std::path::Path; /// File name of the lock file, stored alongside `program.json`. pub const LOCK_FILENAME: &str = "leo.lock"; -const LOCK_VERSION: u32 = 1; +const LOCK_VERSION: u32 = 2; /// A single pinned git dependency. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -39,43 +39,121 @@ pub struct GitLockEntry { pub commit: String, } +/// A program identity obtained from reviewed bytecode or an independent trusted source. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct NetworkLockEntry { + pub name: String, + pub network: String, + pub edition: u16, + /// The SHA3-256 checksum of the canonical Aleo program, as 32 bytes. + pub checksum: [u8; 32], +} + +impl NetworkLockEntry { + /// Verify bytecode before it can enter the cache or dependency graph. + pub fn verify(&self, bytecode: &str) -> Result<()> { + if bytecode.len() > crate::MAX_PROGRAM_SIZE { + return Err(crate::errors::program_size_limit_exceeded( + &self.name, + bytecode.len(), + crate::MAX_PROGRAM_SIZE, + ) + .into()); + } + let program: Program = + bytecode.parse().map_err(|_| crate::errors::snarkvm_parsing_error(crate::bare_unit_name(&self.name)))?; + if program.id().to_string() != self.name { + return Err(crate::errors::untrusted_network_program( + &self.name, + "the bytecode declares a different program ID", + ) + .into()); + } + if program.to_checksum().map(|byte| *byte) != self.checksum { + return Err(crate::errors::untrusted_network_program( + &self.name, + "the bytecode does not match the trusted checksum", + ) + .into()); + } + Ok(()) + } +} + /// The contents of `leo.lock`. #[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct Lock { version: u32, #[serde(default)] git: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + network: Vec, } impl Default for Lock { fn default() -> Self { - Lock { version: LOCK_VERSION, git: Vec::new() } + Lock { version: LOCK_VERSION, git: Vec::new(), network: Vec::new() } } } impl Lock { - /// Read the lock from `dir`, or an empty lock if it is missing. A malformed or unsupported-version - /// lock is regenerated rather than erroring, but warns (unlike a missing file) so lost pins are visible. - pub fn read(dir: &Path) -> Self { + /// Read the lock from `dir`, or an empty lock if it is missing. + pub fn read(dir: &Path) -> Result { let path = dir.join(LOCK_FILENAME); - let Ok(contents) = std::fs::read_to_string(&path) else { - return Lock::default(); + let contents = match std::fs::read_to_string(&path) { + Ok(contents) => contents, + Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(Self::default()), + Err(err) => return Err(crate::errors::invalid_lock_file(path.display(), err).into()), }; - match serde_json::from_str::(&contents) { - Ok(lock) if lock.version == LOCK_VERSION => lock, - Ok(lock) => { - tracing::warn!( - "⚠️ Ignoring `{}`: unsupported lock version {} (expected {LOCK_VERSION}). It will be regenerated.", - path.display(), - lock.version, - ); - Lock::default() + Self::parse(&path, &contents) + } + + /// Read an explicitly selected trust file. Missing files are errors. + pub fn read_file(path: &Path) -> Result { + let contents = + std::fs::read_to_string(path).map_err(|err| crate::errors::invalid_lock_file(path.display(), err))?; + Self::parse(path, &contents) + } + + fn parse(path: &Path, contents: &str) -> Result { + let mut lock: Self = + serde_json::from_str(contents).map_err(|err| crate::errors::invalid_lock_file(path.display(), err))?; + if !matches!(lock.version, 1 | LOCK_VERSION) { + return Err(crate::errors::invalid_lock_file(path.display(), "unsupported lock version").into()); + } + let mut seen = IndexSet::with_capacity(lock.network.len()); + for pin in &lock.network { + if pin.name.parse::>().is_err() + || pin.network.parse::().is_err() + || (pin.name == "credits.aleo" && pin.edition != 0) + { + return Err(crate::errors::invalid_lock_file(path.display(), "invalid network program identity").into()); } - Err(err) => { - tracing::warn!("⚠️ Ignoring malformed `{}` ({err}). It will be regenerated.", path.display()); - Lock::default() + if !seen.insert((&pin.network, &pin.name)) { + return Err(crate::errors::invalid_lock_file(path.display(), "duplicate network program pin").into()); } } + lock.version = LOCK_VERSION; + Ok(lock) + } + + /// Select the trusted edition. Network responses and cache contents cannot select a pin. + pub fn network_pin(&self, name: &str, network: NetworkName, edition: Option) -> Result<&NetworkLockEntry> { + let name = crate::canonicalize_program_name(name); + let network = network.to_string(); + let pin = self.network.iter().find(|pin| pin.name == name && pin.network == network).ok_or_else(|| { + crate::errors::untrusted_network_program(&name, "no trusted checksum pin for this network") + })?; + if edition.is_some_and(|edition| edition != pin.edition) { + return Err(crate::errors::untrusted_network_program( + &name, + "the requested edition does not match the trusted pin", + ) + .into()); + } + Ok(pin) } /// The pinned commit for `(name, git, reference)`, or `None` (forcing re-resolution) on any mismatch. @@ -99,6 +177,8 @@ impl Lock { /// Carry over entries from `old` that were not re-recorded in this lock and that `keep` accepts. pub fn carry_over(&mut self, old: &Lock, mut keep: impl FnMut(&GitLockEntry) -> bool) { + // Network pins are user-managed, including pins not used by this build. + self.network.clone_from(&old.network); for entry in &old.git { if self.commit_for(&entry.name, &entry.git, &entry.reference).is_none() && keep(entry) { self.git.push(entry.clone()); @@ -106,13 +186,12 @@ impl Lock { } } - /// Remove all entries pinning the dependency `name`. + /// Remove Git entries for the dependency `name`, preserving network pins. pub fn remove_name(&mut self, name: &str) { self.git.retain(|e| e.name != name); } - /// Write the lock to `dir`, entries sorted for determinism. With no git dependencies, no file is - /// written and a stale one is removed. + /// Write the lock to `dir`, with entries sorted for deterministic output. pub fn write(&mut self, dir: &Path) -> Result<()> { let path = dir.join(LOCK_FILENAME); if self.is_empty() { @@ -122,6 +201,7 @@ impl Lock { return Ok(()); } self.git.sort_by(|a, b| (&a.name, &a.git, &a.reference).cmp(&(&b.name, &b.git, &b.reference))); + self.network.sort_by(|a, b| (&a.network, &a.name).cmp(&(&b.network, &b.name))); let mut contents = serde_json::to_string_pretty(self) .map_err(|err| crate::errors::failed_to_serialize_lock(path.display(), err))?; @@ -130,8 +210,8 @@ impl Lock { Ok(()) } - /// Whether any git dependency is recorded. + /// Whether the lock has no git entries or network pins. pub fn is_empty(&self) -> bool { - self.git.is_empty() + self.git.is_empty() && self.network.is_empty() } } diff --git a/crates/package/src/package.rs b/crates/package/src/package.rs index aa8bd10e5d7..d1b2ca6b7b3 100644 --- a/crates/package/src/package.rs +++ b/crates/package/src/package.rs @@ -254,6 +254,7 @@ impl Package { network, endpoint, network_retries, + None, ) } @@ -282,6 +283,33 @@ impl Package { network, endpoint, network_retries, + None, + ) + } + + /// Load the package with an optional trusted network lock file. + #[allow(clippy::too_many_arguments)] + pub fn from_aleo_file_with_network_lock, Q: AsRef>( + path: P, + home_path: Q, + imports_directory: Option<&Path>, + no_cache: bool, + no_local: bool, + network: Option, + endpoint: Option<&str>, + network_retries: u32, + network_lock: Option<&Path>, + ) -> Result { + Self::from_aleo_file_impl( + path.as_ref(), + home_path.as_ref(), + imports_directory, + no_cache, + no_local, + network, + endpoint, + network_retries, + network_lock, ) } @@ -309,6 +337,35 @@ impl Package { network, endpoint, network_retries, + None, + ) + } + + /// Load the package with an optional trusted network lock file. + #[allow(clippy::too_many_arguments)] + pub fn from_directory_with_network_lock, Q: AsRef>( + path: P, + home_path: Q, + no_cache: bool, + no_local: bool, + offline: bool, + network: Option, + endpoint: Option<&str>, + network_retries: u32, + network_lock: Option<&Path>, + ) -> Result { + Self::from_directory_impl( + path.as_ref(), + home_path.as_ref(), + /* build_graph */ true, + /* with_tests */ false, + no_cache, + no_local, + offline, + network, + endpoint, + network_retries, + network_lock, ) } @@ -336,6 +393,35 @@ impl Package { network, endpoint, network_retries, + None, + ) + } + + /// Load the package with an optional trusted network lock file. + #[allow(clippy::too_many_arguments)] + pub fn from_directory_with_tests_and_network_lock, Q: AsRef>( + path: P, + home_path: Q, + no_cache: bool, + no_local: bool, + offline: bool, + network: Option, + endpoint: Option<&str>, + network_retries: u32, + network_lock: Option<&Path>, + ) -> Result { + Self::from_directory_impl( + path.as_ref(), + home_path.as_ref(), + /* build_graph */ true, + /* with_tests */ true, + no_cache, + no_local, + offline, + network, + endpoint, + network_retries, + network_lock, ) } @@ -370,6 +456,7 @@ impl Package { network: Option, endpoint: Option<&str>, network_retries: u32, + network_lock: Option<&Path>, ) -> Result { if path.extension().and_then(|extension| extension.to_str()) != Some("aleo") { return Err(anyhow!("Expected an Aleo bytecode file with the `.aleo` extension: {}", path.display()).into()); @@ -423,6 +510,10 @@ impl Package { let mut map: IndexMap = IndexMap::new(); let mut digraph = DiGraph::new(Default::default()); let old_lock = Lock::default(); + let network_pins = match network_lock { + Some(path) => Lock::read_file(path)?, + None => Lock::read(&base_directory)?, + }; let mut new_lock = Lock::default(); Self::graph_build( &home_path, @@ -438,6 +529,7 @@ impl Package { network_retries, &declared_deps, &old_lock, + &network_pins, &mut new_lock, false, )?; @@ -478,6 +570,7 @@ impl Package { network: Option, endpoint: Option<&str>, network_retries: u32, + network_lock: Option<&Path>, ) -> Result { let map_err = |path: &Path, err| { crate::errors::util_file_io_error(format_args!("Trying to find path at {}", path.display()), err) @@ -506,7 +599,9 @@ impl Package { // The lock lives at the workspace root, else beside this package's `program.json`. let lock_dir = workspace_root.as_deref().unwrap_or(&path).to_path_buf(); // New lock records only this build's resolutions; others are carried over from the old lock after. - let old_lock = Lock::read(&lock_dir); + let old_lock = Lock::read(&lock_dir)?; + let override_lock = network_lock.map(Lock::read_file).transpose()?; + let network_pins = override_lock.as_ref().unwrap_or(&old_lock); let mut new_lock = Lock::default(); let first_dependency = Dependency { @@ -562,6 +657,7 @@ impl Package { network_retries, &declared_deps, &old_lock, + network_pins, &mut new_lock, offline, )?; @@ -617,6 +713,7 @@ impl Package { network_retries: u32, declared_deps: &IndexMap, old_lock: &Lock, + network_pins: &Lock, new_lock: &mut Lock, offline: bool, ) -> Result<()> { @@ -700,6 +797,7 @@ impl Package { endpoint, no_cache, network_retries, + network_pins, )? } (_, Location::Git) => CompilationUnit::from_git( @@ -774,6 +872,7 @@ impl Package { network_retries, declared_deps, old_lock, + network_pins, new_lock, offline, )?; @@ -1125,6 +1224,18 @@ function main: DEPENDENCY_PROGRAM, ); + let dependency: SvmProgram = DEPENDENCY_PROGRAM.parse().expect("fixture program must parse"); + crate::test_util::write_file( + &root.join(LOCK_FILENAME), + &serde_json::json!({"version": 2, "network": [{ + "name": "dependency.aleo", + "network": "testnet", + "edition": 0, + "checksum": dependency.to_checksum().map(|byte| *byte), + }]}) + .to_string(), + ); + let package = Package::from_aleo_file( &program_path, &home, diff --git a/crates/package/src/tests.rs b/crates/package/src/tests.rs index 29d7ff752d9..25164b84694 100644 --- a/crates/package/src/tests.rs +++ b/crates/package/src/tests.rs @@ -14,8 +14,7 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -//! Tests for git-dependency support: reference resolution, the `leo.lock` lock file, -//! manifest validation, end-to-end resolution, and workspace lock sharing. +//! Tests for dependency resolution, trusted network bytecode, lock files, manifests, and workspaces. use crate::{ GitReference, @@ -43,6 +42,514 @@ use crate::{ use leo_span::Symbol; +use snarkvm::prelude::{Program, TestnetV0}; +use std::{ + io::{Read, Write}, + net::TcpListener, + path::Path, + thread::{self, JoinHandle}, + time::{Duration, Instant}, +}; + +const TRUSTED_TOKEN: &str = + "program token.aleo;\nfunction reveal:\n input r0 as u32.private;\n output r0 as u32.private;\n"; + +fn network_pin(bytecode: &str, edition: u16) -> serde_json::Value { + let program: Program = bytecode.parse().expect("fixture bytecode must parse"); + serde_json::json!({ + "name": program.id().to_string(), + "network": "testnet", + "edition": edition, + "checksum": program.to_checksum().map(|byte| *byte), + }) +} + +fn write_network_lock(directory: &Path, pins: &[serde_json::Value]) -> Lock { + let contents = serde_json::json!({"version": 2, "git": [], "network": pins}); + write_file(&directory.join(LOCK_FILENAME), &contents.to_string()); + Lock::read(directory).expect("fixture lock must be valid") +} + +fn network_response(status: &str, body: &str) -> (String, JoinHandle>) { + let listener = TcpListener::bind("127.0.0.1:0").expect("fixture listener must bind"); + listener.set_nonblocking(true).expect("fixture listener must be nonblocking"); + let endpoint = format!("http://{}", listener.local_addr().expect("fixture address must exist")); + let response = format!( + "HTTP/1.1 {status}\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}", + body.len() + ); + let handle = thread::spawn(move || { + let mut requests = Vec::new(); + let mut deadline = Instant::now() + Duration::from_secs(2); + while Instant::now() < deadline { + match listener.accept() { + Ok((mut stream, _)) => { + stream.set_nonblocking(false).expect("fixture stream must use blocking I/O"); + stream.set_read_timeout(Some(Duration::from_secs(1))).expect("read timeout must be set"); + stream.set_write_timeout(Some(Duration::from_secs(1))).expect("write timeout must be set"); + let mut request = Vec::new(); + let mut buffer = [0; 1024]; + while !request.windows(4).any(|bytes| bytes == b"\r\n\r\n") { + let count = stream.read(&mut buffer).expect("fixture request must arrive"); + assert!(count > 0 && request.len() < 8192, "fixture request must contain bounded headers"); + request.extend_from_slice(&buffer[..count]); + } + requests.push(String::from_utf8(request).expect("fixture request must be UTF-8")); + stream.write_all(response.as_bytes()).expect("fixture response must be sent"); + deadline = Instant::now() + Duration::from_millis(200); + assert!(requests.len() < 10, "fetch must not make unbounded requests"); + } + Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => { + thread::sleep(Duration::from_millis(10)); + } + Err(error) => panic!("fixture accept failed: {error}"), + } + } + requests + }); + (endpoint, handle) +} + +#[test] +fn cached_network_program_requires_trusted_pin() { + leo_span::create_session_if_not_set_then(|_| { + let home = unique_dir("network-pin-required"); + write_file( + &home.join("registry/testnet/token/0/token.aleo"), + "program token.aleo;\nfunction reveal:\n input r0 as u32.public;\n output r0 as u32.public;\n", + ); + let result = crate::CompilationUnit::fetch( + Symbol::intern("token.aleo"), + Some(0), + &home, + leo_ast::NetworkName::TestnetV0, + "http://127.0.0.1:1", + false, + 0, + &Lock::default(), + ); + std::fs::remove_dir_all(home).expect("test directory must be removed"); + assert!(result.is_err(), "cached bytecode must not be trusted without an independent pin"); + }); +} + +#[test] +fn cached_network_program_checks_canonical_bytes_on_every_read() { + leo_span::create_session_if_not_set_then(|_| { + let home = unique_dir("network-cache-verification"); + let lock = write_network_lock(&home, &[network_pin(TRUSTED_TOKEN, 0)]); + let cache = home.join("registry/testnet/token/0/token.aleo"); + let formatted = format!("// Independent formatting does not change canonical bytes.\n{TRUSTED_TOKEN}\n"); + write_file(&cache, &formatted); + let unit = crate::CompilationUnit::fetch( + Symbol::intern("token.aleo"), + Some(0), + &home, + leo_ast::NetworkName::TestnetV0, + "http://127.0.0.1:1", + false, + 0, + &lock, + ) + .expect("approved canonical bytecode must load from cache"); + assert_eq!(unit.edition, Some(0)); + assert!(matches!(unit.data, crate::ProgramData::Bytecode(ref bytecode) if bytecode == &formatted)); + + for modified in [ + TRUSTED_TOKEN.replace("u32.private", "u32.public"), + TRUSTED_TOKEN.replace("output r0", "add r0 1u32 into r1;\n output r1"), + ] { + write_file(&cache, &modified); + let error = crate::CompilationUnit::fetch( + Symbol::intern("token.aleo"), + Some(0), + &home, + leo_ast::NetworkName::TestnetV0, + "http://127.0.0.1:1", + false, + 0, + &lock, + ) + .expect_err("changed same-ID bytecode must fail on the next read"); + assert!(error.to_string().contains("checksum"), "{error}"); + assert_eq!(std::fs::read_to_string(&cache).expect("cache must remain readable"), modified); + } + std::fs::remove_dir_all(home).expect("test directory must be removed"); + }); +} + +#[test] +fn inferred_network_edition_comes_from_pin_not_cache() { + leo_span::create_session_if_not_set_then(|_| { + let home = unique_dir("network-pinned-edition"); + let lock = write_network_lock(&home, &[network_pin(TRUSTED_TOKEN, 2)]); + write_file(&home.join("registry/testnet/token/2/token.aleo"), TRUSTED_TOKEN); + write_file(&home.join("registry/testnet/token/99/token.aleo"), &TRUSTED_TOKEN.replace("private", "public")); + let unit = crate::CompilationUnit::fetch( + Symbol::intern("token.aleo"), + None, + &home, + leo_ast::NetworkName::TestnetV0, + "http://127.0.0.1:1", + false, + 0, + &lock, + ) + .expect("the trusted edition must load without a latest-edition request"); + assert_eq!(unit.edition, Some(2)); + assert!(matches!(unit.data, crate::ProgramData::Bytecode(ref bytecode) if bytecode == TRUSTED_TOKEN)); + std::fs::remove_dir_all(home).expect("test directory must be removed"); + }); +} + +#[test] +fn network_pin_mismatch_fails_before_io() { + leo_span::create_session_if_not_set_then(|_| { + let home = unique_dir("network-pin-mismatch"); + let lock = write_network_lock(&home, &[network_pin(TRUSTED_TOKEN, 2)]); + let (endpoint, server) = network_response("200 OK", TRUSTED_TOKEN); + for (name, network, edition) in [ + ("token.aleo", leo_ast::NetworkName::MainnetV0, Some(2)), + ("token.aleo", leo_ast::NetworkName::TestnetV0, Some(3)), + ("missing.aleo", leo_ast::NetworkName::TestnetV0, None), + ] { + let error = crate::CompilationUnit::fetch( + Symbol::intern(name), + edition, + &home, + network, + &endpoint, + false, + 0, + &lock, + ) + .expect_err("missing or mismatched pins must fail before fetching"); + assert!(error.to_string().contains("pin"), "{error}"); + } + assert!( + server.join().expect("fixture must finish").is_empty(), + "untrusted requests must not reach the endpoint" + ); + assert!(!home.join("registry").exists()); + std::fs::remove_dir_all(home).expect("test directory must be removed"); + }); +} + +#[test] +fn network_fetch_uses_exact_pinned_edition() { + leo_span::create_session_if_not_set_then(|_| { + let home = unique_dir("network-exact-edition"); + let lock = write_network_lock(&home, &[network_pin(TRUSTED_TOKEN, 7)]); + let body = serde_json::to_string(TRUSTED_TOKEN).expect("fixture body must serialize"); + let (endpoint, server) = network_response("200 OK", &body); + let unit = crate::CompilationUnit::fetch( + Symbol::intern("token.aleo"), + None, + &home, + leo_ast::NetworkName::TestnetV0, + &endpoint, + false, + 0, + &lock, + ) + .expect("approved response must load"); + let requests = server.join().expect("fixture must finish"); + assert_eq!(requests.len(), 1); + assert!(requests[0].starts_with("GET /testnet/program/token.aleo/7 HTTP/1.1\r\n"), "{:?}", requests); + assert_eq!(unit.edition, Some(7)); + assert_eq!( + std::fs::read_to_string(home.join("registry/testnet/token/7/token.aleo")) + .expect("approved cache must exist"), + TRUSTED_TOKEN + ); + std::fs::remove_dir_all(home).expect("test directory must be removed"); + }); +} + +#[test] +fn rejected_network_responses_do_not_replace_cache_or_fall_back() { + leo_span::create_session_if_not_set_then(|_| { + for (status, bytecode, checksum_source, reason) in [ + ("200 OK", TRUSTED_TOKEN.replace("private", "public"), TRUSTED_TOKEN.to_owned(), "checksum"), + ( + "200 OK", + TRUSTED_TOKEN.replace("output r0", "add r0 1u32 into r1;\n output r1"), + TRUSTED_TOKEN.to_owned(), + "checksum", + ), + ( + "200 OK", + TRUSTED_TOKEN.replace("token.aleo", "other.aleo"), + TRUSTED_TOKEN.replace("token.aleo", "other.aleo"), + "program ID", + ), + ("404 Not Found", "missing edition".to_owned(), TRUSTED_TOKEN.to_owned(), ""), + ] { + let home = unique_dir("network-rejected-response"); + let mut pin = network_pin(&checksum_source, 7); + pin["name"] = serde_json::json!("token.aleo"); + let lock = write_network_lock(&home, &[pin]); + let cache = home.join("registry/testnet/token/7/token.aleo"); + write_file(&cache, TRUSTED_TOKEN); + let (endpoint, server) = network_response(status, &bytecode); + let error = crate::CompilationUnit::fetch( + Symbol::intern("token.aleo"), + Some(7), + &home, + leo_ast::NetworkName::TestnetV0, + &endpoint, + true, + 0, + &lock, + ) + .expect_err("an unapproved response must fail"); + assert!(error.to_string().contains(reason), "{error}"); + let requests = server.join().expect("fixture must finish"); + assert_eq!(requests.len(), 1, "no latest-edition request or unversioned fallback is permitted"); + assert!(requests[0].starts_with("GET /testnet/program/token.aleo/7 HTTP/1.1\r\n")); + assert_eq!(std::fs::read_to_string(cache).expect("old cache must exist"), TRUSTED_TOKEN); + std::fs::remove_dir_all(home).expect("test directory must be removed"); + } + }); +} + +#[test] +fn rejected_network_response_does_not_create_a_cache() { + leo_span::create_session_if_not_set_then(|_| { + let home = unique_dir("network-rejected-new-cache"); + let lock = write_network_lock(&home, &[network_pin(TRUSTED_TOKEN, 0)]); + let (endpoint, server) = network_response("200 OK", &TRUSTED_TOKEN.replace("private", "public")); + let error = crate::CompilationUnit::fetch( + Symbol::intern("token.aleo"), + Some(0), + &home, + leo_ast::NetworkName::TestnetV0, + &endpoint, + false, + 0, + &lock, + ) + .expect_err("an unapproved response must not enter a new cache"); + assert!(error.to_string().contains("checksum"), "{error}"); + assert_eq!(server.join().expect("fixture must finish").len(), 1); + assert!(!home.join("registry").exists()); + std::fs::remove_dir_all(home).expect("test directory must be removed"); + }); +} + +#[test] +fn bundled_credits_ignores_hostile_cache() { + leo_span::create_session_if_not_set_then(|_| { + let home = unique_dir("network-bundled-credits"); + let cache = home.join("registry/testnet/credits/0/credits.aleo"); + let hostile = TRUSTED_TOKEN.replace("token.aleo", "credits.aleo"); + write_file(&cache, &hostile); + let unit = crate::CompilationUnit::fetch( + Symbol::intern("credits.aleo"), + None, + &home, + leo_ast::NetworkName::TestnetV0, + "http://127.0.0.1:1", + false, + 0, + &Lock::default(), + ) + .expect("bundled credits must work without a pin or endpoint"); + let expected = Program::::credits().expect("bundled credits must exist").to_string(); + assert!(matches!(unit.data, crate::ProgramData::Bytecode(ref bytecode) if bytecode == &expected)); + assert_eq!(unit.edition, Some(0)); + assert_eq!(std::fs::read_to_string(cache).expect("hostile cache must remain unchanged"), hostile); + assert!( + crate::CompilationUnit::fetch( + Symbol::intern("credits.aleo"), + Some(1), + &home, + leo_ast::NetworkName::TestnetV0, + "http://127.0.0.1:1", + false, + 0, + &Lock::default(), + ) + .is_err() + ); + std::fs::remove_dir_all(home).expect("test directory must be removed"); + }); +} + +#[test] +fn network_lock_rejects_malformed_and_ambiguous_trust() { + let directory = unique_dir("network-lock-invalid"); + let pin = network_pin(TRUSTED_TOKEN, 0); + let mut other_edition = pin.clone(); + other_edition["edition"] = serde_json::json!(1); + let mut short_checksum = pin.clone(); + short_checksum["checksum"] = serde_json::json!([1, 2, 3]); + for contents in [ + "{".to_owned(), + serde_json::json!({"version": 99, "git": []}).to_string(), + serde_json::json!({"version": 2, "network": [pin.clone(), pin.clone()]}).to_string(), + serde_json::json!({"version": 2, "network": [pin, other_edition]}).to_string(), + serde_json::json!({"version": 2, "network": [short_checksum]}).to_string(), + serde_json::json!({"version": 2, "netwrok": []}).to_string(), + ] { + write_file(&directory.join(LOCK_FILENAME), &contents); + assert!(Lock::read(&directory).is_err(), "invalid trust file accepted: {contents}"); + assert!(Lock::read_file(&directory.join(LOCK_FILENAME)).is_err()); + assert_eq!(std::fs::read_to_string(directory.join(LOCK_FILENAME)).expect("invalid file must remain"), contents); + } + std::fs::remove_file(directory.join(LOCK_FILENAME)).expect("invalid fixture must be removed"); + assert!(Lock::read(&directory).expect("missing implicit lock is empty").is_empty()); + assert!(Lock::read_file(&directory.join(LOCK_FILENAME)).is_err(), "an explicit trust file must exist"); + std::fs::remove_dir_all(directory).expect("test directory must be removed"); +} + +#[test] +fn legacy_git_lock_is_readable_and_network_pins_survive_git_updates() { + let directory = unique_dir("network-lock-preservation"); + write_file( + &directory.join(LOCK_FILENAME), + r#"{"version":1,"git":[{"name":"legacy","git":"url","reference":"default","commit":"abc"}]}"#, + ); + let legacy = Lock::read(&directory).expect("version one Git lock must load"); + assert_eq!(legacy.commit_for("legacy", "url", "default"), Some("abc")); + let mut old = write_network_lock(&directory, &[network_pin(TRUSTED_TOKEN, 2)]); + old.record("token.aleo".into(), "url".into(), "default".into(), "old".into()); + let mut updated = Lock::default(); + updated.record("other".into(), "url2".into(), "default".into(), "new".into()); + updated.carry_over(&old, |_| false); + updated.remove_name("token.aleo"); + updated.write(&directory).expect("updated lock must write"); + let reloaded = Lock::read(&directory).expect("updated lock must load"); + let pin = reloaded + .network_pin("token.aleo", leo_ast::NetworkName::TestnetV0, Some(2)) + .expect("network pin must survive Git changes"); + pin.verify(TRUSTED_TOKEN).expect("network checksum must remain unchanged"); + assert!(reloaded.commit_for("token.aleo", "url", "default").is_none()); + assert_eq!(reloaded.commit_for("other", "url2", "default"), Some("new")); + updated.remove_name("other"); + updated.write(&directory).expect("a lock with only network pins must write"); + assert!(!Lock::read(&directory).expect("network-only lock must remain").is_empty()); + std::fs::remove_dir_all(directory).expect("test directory must be removed"); +} + +#[test] +fn package_requires_a_pin_for_each_transitive_network_import() { + leo_span::create_session_if_not_set_then(|_| { + let base = unique_dir("network-transitive-pins"); + let home = base.join("home"); + let consumer = base.join("consumer"); + let parent = format!("import token.aleo;\n{}", TRUSTED_TOKEN.replace("token.aleo", "parent.aleo")); + write_consumer(&consumer, r#"{"name":"parent.aleo","location":"network","edition":0}"#); + write_file(&home.join("registry/testnet/parent/0/parent.aleo"), &parent); + write_file(&home.join("registry/testnet/token/0/token.aleo"), TRUSTED_TOKEN); + write_network_lock(&consumer, &[network_pin(&parent, 0)]); + let error = Package::from_directory( + &consumer, + &home, + false, + false, + false, + Some(leo_ast::NetworkName::TestnetV0), + Some("http://127.0.0.1:1"), + 0, + ) + .expect_err("a pinned parent must not confer trust on its imports"); + assert!(error.to_string().contains("token.aleo"), "{error}"); + assert!(error.to_string().contains("pin"), "{error}"); + write_network_lock(&consumer, &[network_pin(&parent, 0), network_pin(TRUSTED_TOKEN, 0)]); + let package = Package::from_directory( + &consumer, + &home, + false, + false, + false, + Some(leo_ast::NetworkName::TestnetV0), + Some("http://127.0.0.1:1"), + 0, + ) + .expect("each pinned transitive dependency must load offline from cache"); + assert_eq!(package.compilation_units.len(), 3); + let names: Vec<_> = package.compilation_units.iter().map(|unit| unit.name.to_string()).collect(); + assert_eq!(names, ["token.aleo", "parent.aleo", "consumer.aleo"]); + let local = base.join("local"); + write_program(&local, "local.aleo", "null"); + let package = Package::from_directory(&local, &home, false, false, false, None, None, 0) + .expect("a local-only package must not require network pins"); + assert_eq!(package.compilation_units.len(), 1); + std::fs::remove_dir_all(base).expect("test directory must be removed"); + }); +} + +#[test] +fn package_uses_workspace_pins_and_keeps_explicit_override_read_only() { + leo_span::create_session_if_not_set_then(|_| { + let base = unique_dir("network-workspace-override"); + let home = base.join("home"); + let workspace = base.join("workspace"); + let consumer = workspace.join("consumer"); + write_file(&workspace.join(WORKSPACE_MANIFEST_FILENAME), r#"{"members":["consumer"]}"#); + write_consumer(&consumer, r#"{"name":"token.aleo","location":"network","edition":0}"#); + write_file(&home.join("registry/testnet/token/0/token.aleo"), TRUSTED_TOKEN); + write_network_lock(&workspace, &[network_pin(TRUSTED_TOKEN, 0)]); + write_file(&consumer.join(LOCK_FILENAME), "ignored member lock"); + Package::from_directory( + &consumer, + &home, + false, + false, + false, + Some(leo_ast::NetworkName::TestnetV0), + Some("http://127.0.0.1:1"), + 0, + ) + .expect("workspace root must supply network trust"); + assert_eq!( + std::fs::read_to_string(consumer.join(LOCK_FILENAME)).expect("member file must remain"), + "ignored member lock" + ); + let approved = base.join("approved"); + write_network_lock(&approved, &[network_pin(TRUSTED_TOKEN, 0)]); + let override_path = approved.join(LOCK_FILENAME); + let override_contents = std::fs::read(&override_path).expect("approved file must exist"); + let conflicting = TRUSTED_TOKEN.replace("private", "public"); + write_network_lock(&workspace, &[network_pin(&conflicting, 0)]); + assert!( + Package::from_directory( + &consumer, + &home, + false, + false, + false, + Some(leo_ast::NetworkName::TestnetV0), + Some("http://127.0.0.1:1"), + 0, + ) + .is_err(), + "the workspace pin must reject a different checksum" + ); + Package::from_directory_with_network_lock( + &consumer, + &home, + false, + false, + false, + Some(leo_ast::NetworkName::TestnetV0), + Some("http://127.0.0.1:1"), + 0, + Some(&override_path), + ) + .expect("the explicit trust file must select the approved checksum"); + assert_eq!(std::fs::read(&override_path).expect("approved file must remain"), override_contents); + Lock::read(&workspace) + .expect("workspace lock must remain valid") + .network_pin("token.aleo", leo_ast::NetworkName::TestnetV0, Some(0)) + .expect("workspace pin must remain") + .verify(&conflicting) + .expect("the override must not replace the workspace pin"); + std::fs::remove_dir_all(base).expect("test directory must be removed"); + }); +} + // Reference resolution (`crate::git::resolve`). #[test] @@ -402,13 +909,13 @@ fn find_in_checkout_does_not_follow_symlinks() { #[test] fn round_trip_and_lookup() { let dir = unique_dir("lock"); - let mut lock = Lock::read(&dir); + let mut lock = Lock::read(&dir).expect("lock must be valid"); assert!(lock.is_empty()); lock.record("foo.aleo".into(), "https://example.com/foo".into(), "tag=v1".into(), "abc123".into()); lock.write(&dir).unwrap(); - let reloaded = Lock::read(&dir); + let reloaded = Lock::read(&dir).expect("lock must be valid"); assert_eq!(reloaded.commit_for("foo.aleo", "https://example.com/foo", "tag=v1"), Some("abc123")); // Reference mismatch forces re-resolution. assert_eq!(reloaded.commit_for("foo.aleo", "https://example.com/foo", "tag=v2"), None); @@ -606,7 +1113,7 @@ fn git_dependency_resolves_and_locks() { assert!(lib_unit.kind.is_library()); // The lock file was written and pins the library to a commit. - let lock = Lock::read(&consumer); + let lock = Lock::read(&consumer).expect("lock must be valid"); let commit = lock.commit_for("mylib", &url, "default").expect("lock pins mylib"); assert_eq!(commit.len(), 40); @@ -652,7 +1159,10 @@ fn git_dev_dependency_resolves_with_tests() { package.compilation_units.iter().any(|u| u.name == Symbol::intern("mylib")), "git dev-dependency resolved", ); - assert!(Lock::read(&consumer).commit_for("mylib", &url, "default").is_some(), "dev-dependency locked"); + assert!( + Lock::read(&consumer).expect("lock must be valid").commit_for("mylib", &url, "default").is_some(), + "dev-dependency locked" + ); }); let _ = std::fs::remove_dir_all(&root); @@ -681,14 +1191,21 @@ fn offline_build_uses_locked_commit_and_cache() { leo_span::create_session_if_not_set_then(|_| { // Build online once to populate the lock and the checkout cache. Package::from_directory(&consumer, &home, false, false, false, None, None, 3).unwrap(); - let commit = Lock::read(&consumer).commit_for("mylib", &url, "default").expect("locked").to_string(); + let commit = Lock::read(&consumer) + .expect("lock must be valid") + .commit_for("mylib", &url, "default") + .expect("locked") + .to_string(); // Make the source repository unreachable; any fetch would now fail. std::fs::remove_dir_all(&lib).unwrap(); // The offline build reuses the locked commit from the cache. Package::from_directory(&consumer, &home, false, false, true, None, None, 3).unwrap(); - assert_eq!(Lock::read(&consumer).commit_for("mylib", &url, "default"), Some(commit.as_str())); + assert_eq!( + Lock::read(&consumer).expect("lock must be valid").commit_for("mylib", &url, "default"), + Some(commit.as_str()) + ); }); let _ = std::fs::remove_dir_all(&root); @@ -773,9 +1290,16 @@ fn plain_build_keeps_dev_dependency_pin() { leo_span::create_session_if_not_set_then(|_| { // A test build records the dev pin; a subsequent plain build must carry it over. Package::from_directory_with_tests(&consumer, &home, false, false, false, None, None, 3).unwrap(); - let commit = Lock::read(&consumer).commit_for("mylib", &url, "default").expect("locked").to_string(); + let commit = Lock::read(&consumer) + .expect("lock must be valid") + .commit_for("mylib", &url, "default") + .expect("locked") + .to_string(); Package::from_directory(&consumer, &home, false, false, false, None, None, 3).unwrap(); - assert_eq!(Lock::read(&consumer).commit_for("mylib", &url, "default"), Some(commit.as_str())); + assert_eq!( + Lock::read(&consumer).expect("lock must be valid").commit_for("mylib", &url, "default"), + Some(commit.as_str()) + ); // Once the dev dependency is gone from the manifest, the plain build prunes its pin. write_file( @@ -810,7 +1334,7 @@ fn git_dependency_ref_change_updates_lock() { // Track the default branch first. write_consumer(&consumer, &format!(r#"{{"name":"mylib","location":"git","git":{{"url":"{url}"}}}}"#)); Package::from_directory(&consumer, &home, false, false, false, None, None, 3).unwrap(); - assert!(Lock::read(&consumer).commit_for("mylib", &url, "default").is_some()); + assert!(Lock::read(&consumer).expect("lock must be valid").commit_for("mylib", &url, "default").is_some()); // Re-pin to the tag: the lock gains the tag entry and drops the stale default one. write_consumer( @@ -818,7 +1342,7 @@ fn git_dependency_ref_change_updates_lock() { &format!(r#"{{"name":"mylib","location":"git","git":{{"url":"{url}","tag":"v1"}}}}"#), ); Package::from_directory(&consumer, &home, false, false, false, None, None, 3).unwrap(); - let lock = Lock::read(&consumer); + let lock = Lock::read(&consumer).expect("lock must be valid"); assert!(lock.commit_for("mylib", &url, "tag=v1").is_some(), "tag entry recorded"); assert!(lock.commit_for("mylib", &url, "default").is_none(), "stale default entry pruned"); }); @@ -857,7 +1381,7 @@ fn transitive_git_dependency() { assert!(names.iter().any(|n| n == "liba"), "liba resolved: {names:?}"); assert!(names.iter().any(|n| n == "libb"), "transitive libb resolved: {names:?}"); - let lock = Lock::read(&consumer); + let lock = Lock::read(&consumer).expect("lock must be valid"); assert!(lock.commit_for("liba", &url_a, "default").is_some()); assert!(lock.commit_for("libb", &url_b, "default").is_some()); }); @@ -912,7 +1436,7 @@ fn transitive_git_program_depends_on_library() { .expect("transitive git library resolved"); assert!(lib.kind.is_library()); - let lock = Lock::read(&consumer); + let lock = Lock::read(&consumer).expect("lock must be valid"); assert!(lock.commit_for("midprog.aleo", &url_prog, "default").is_some()); assert!(lock.commit_for("deeplib", &url_lib, "default").is_some()); }); @@ -954,7 +1478,7 @@ fn git_dependency_into_workspace_repo_resolves_member_and_sibling() { // The sibling is rewritten to a git dependency on the same source, so both are locked // (to the same commit, since the repository is resolved once per build). - let lock = Lock::read(&consumer); + let lock = Lock::read(&consumer).expect("lock must be valid"); let libb_commit = lock.commit_for("libb", &url, "default").expect("libb locked"); assert_eq!(lock.commit_for("liba", &url, "default"), Some(libb_commit)); }); @@ -1117,7 +1641,7 @@ fn workspace_members_share_lock_without_clobbering() { Package::from_directory(&memb, &home, false, false, false, None, None, 3).unwrap(); // The shared lock at the workspace root retains both members' entries. - let lock = Lock::read(&ws); + let lock = Lock::read(&ws).expect("lock must be valid"); assert!(lock.commit_for("liba", &url_a, "default").is_some(), "first member's entry retained"); assert!(lock.commit_for("libb", &url_b, "default").is_some(), "second member's entry recorded"); }); @@ -1166,7 +1690,7 @@ fn workspace_members_keep_different_references_to_same_repo() { for member in [&mema, &memb, &mema, &memb] { Package::from_directory(member, &home, false, false, false, None, None, 3).unwrap(); } - let lock = Lock::read(&ws); + let lock = Lock::read(&ws).expect("lock must be valid"); assert!(lock.commit_for("shared", &url, "tag=v1").is_some(), "tag entry retained"); assert!(lock.commit_for("shared", &url, "default").is_some(), "default entry retained"); }); diff --git a/tests/expectations/cli/network_dependency/COMMANDS b/tests/expectations/cli/network_dependency/COMMANDS index e28665b60bb..9dec5105357 100755 --- a/tests/expectations/cli/network_dependency/COMMANDS +++ b/tests/expectations/cli/network_dependency/COMMANDS @@ -1,8 +1,35 @@ #!/usr/bin/env bash LEO_BIN=${1} + +# These checksums come from the reviewed, locally compiled bytecode in this fixture's expectations. +NETWORK_LOCK=$(mktemp) +trap 'rm -f "$NETWORK_LOCK"' EXIT +cat > "$NETWORK_LOCK" <<'JSON' +{"version": 2, "git": [], "network": [ + { + "name": "test_program1.aleo", + "network": "testnet", + "edition": 0, + "checksum": [ + 192, 225, 38, 151, 139, 155, 148, 209, 105, 110, 246, 83, 90, 243, 78, 35, + 155, 186, 242, 17, 196, 169, 179, 144, 71, 227, 34, 207, 121, 158, 34, 25 + ] + }, + { + "name": "test_program2.aleo", + "network": "testnet", + "edition": 0, + "checksum": [ + 206, 255, 111, 48, 59, 85, 122, 166, 182, 205, 10, 141, 58, 23, 70, 232, + 105, 221, 83, 216, 186, 14, 207, 24, 13, 221, 236, 0, 143, 199, 151, 2 + ] + } +]} +JSON PRIVATE_KEY="APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH" COMMON_OPTS="-y --disable-update-check --broadcast --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" +COMMON_OPTS="--network-lock $NETWORK_LOCK $COMMON_OPTS" cd test_program1 || exit 1 echo "DEPLOYMENT 1" diff --git a/tests/expectations/cli/test_add_dependency/STDERR b/tests/expectations/cli/test_add_dependency/STDERR index 64d14671d04..c7b00170ee4 100644 --- a/tests/expectations/cli/test_add_dependency/STDERR +++ b/tests/expectations/cli/test_add_dependency/STDERR @@ -22,9 +22,9 @@ Error [ECLI0377045]: Could not read `../credits/program.json` — is `../credits --- 7. leo add --dev for a program not on the network (should fail) --- Leo ⚠️ No network specified, defaulting to 'testnet'. -Error [ECLI0377045]: Could not find program `dev_dep.aleo` on network `testnet` at `http://localhost:3030`: Error [EPAK0375067]: network request to `http://localhost:3030/testnet/program/dev_dep.aleo/latest_edition` failed with status `500 Internal Server Error` +Error [ECLI0377045]: Could not find program `dev_dep.aleo` on network `testnet` at `http://localhost:3030`: Error [EPAK0375085]: cannot trust network program `dev_dep.aleo`: no trusted checksum pin for this network | - = Verify that `--network` and `--endpoint` point to a running node and that you have connectivity. + = Add the program's network, edition, and checksum from reviewed bytecode or an independent trusted source to `leo.lock`. Use `--network-lock ` to select a trusted lock file. --- 8. leo add --dev for a program on the network (should succeed) --- Leo ⚠️ No network specified, defaulting to 'testnet'. diff --git a/tests/expectations/cli/test_deploy/COMMANDS b/tests/expectations/cli/test_deploy/COMMANDS index 65e8b991378..d531c462d72 100755 --- a/tests/expectations/cli/test_deploy/COMMANDS +++ b/tests/expectations/cli/test_deploy/COMMANDS @@ -3,8 +3,26 @@ set -e LEO_BIN=${1} + +# This checksum comes from the reviewed, locally compiled bytecode in this fixture's expectations. +NETWORK_LOCK=$(mktemp) +trap 'rm -f "$NETWORK_LOCK"' EXIT +cat > "$NETWORK_LOCK" <<'JSON' +{"version": 2, "git": [], "network": [ + { + "name": "some_sample_leo_program.aleo", + "network": "testnet", + "edition": 0, + "checksum": [ + 218, 29, 76, 10, 129, 17, 226, 52, 243, 166, 142, 129, 90, 12, 147, 224, + 200, 219, 166, 88, 40, 193, 250, 177, 40, 42, 100, 97, 232, 64, 198, 156 + ] + } +]} +JSON PRIVATE_KEY="APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH" COMMON_OPTS="--disable-update-check --broadcast --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" +COMMON_OPTS="--network-lock $NETWORK_LOCK $COMMON_OPTS" ALEO_FILE="./build/some_sample_leo_program/some_sample_leo_program.aleo" $LEO_BIN build --disable-update-check --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} diff --git a/tests/expectations/cli/test_deploy_rename/COMMANDS b/tests/expectations/cli/test_deploy_rename/COMMANDS index 0106c95b27f..1643cad16b6 100755 --- a/tests/expectations/cli/test_deploy_rename/COMMANDS +++ b/tests/expectations/cli/test_deploy_rename/COMMANDS @@ -1,9 +1,27 @@ #!/usr/bin/env bash LEO_BIN=${1} + +# These checksums come from the reviewed, locally compiled bytecode in this fixture's expectations. +NETWORK_LOCK=$(mktemp) +trap 'rm -f "$NETWORK_LOCK"' EXIT +cat > "$NETWORK_LOCK" <<'JSON' +{"version": 2, "git": [], "network": [ + { + "name": "renamed_prog.aleo", + "network": "testnet", + "edition": 0, + "checksum": [ + 160, 166, 60, 22, 228, 140, 165, 241, 171, 140, 243, 102, 94, 227, 44, 139, + 3, 205, 179, 211, 213, 124, 14, 113, 28, 135, 160, 183, 198, 61, 226, 215 + ] + } +]} +JSON PRIVATE_KEY="APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH" ENDPOINT="http://localhost:${LEO_DEVNODE_PORT:-3030}" COMMON_OPTS="--disable-update-check --network testnet --endpoint $ENDPOINT --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" +COMMON_OPTS="--network-lock $NETWORK_LOCK $COMMON_OPTS" # The package declares `original_prog.aleo`, but `--rename` recompiles and deploys # it on-chain under a different identity. diff --git a/tests/expectations/cli/test_git_dependency/STDOUT b/tests/expectations/cli/test_git_dependency/STDOUT index 88268e36ff7..b6f6f40894b 100644 --- a/tests/expectations/cli/test_git_dependency/STDOUT +++ b/tests/expectations/cli/test_git_dependency/STDOUT @@ -97,7 +97,7 @@ exit: 0 build exit: 0 leo.lock: { - "version": 1, + "version": 2, "git": [ { "name": "gitbytes.aleo", @@ -133,7 +133,7 @@ exit: 213 exit: 0 leo.lock after remove: { - "version": 1, + "version": 2, "git": [ { "name": "gitbytes.aleo", diff --git a/tests/expectations/cli/test_json/COMMANDS b/tests/expectations/cli/test_json/COMMANDS index 85181daf885..5617ace4f15 100755 --- a/tests/expectations/cli/test_json/COMMANDS +++ b/tests/expectations/cli/test_json/COMMANDS @@ -1,8 +1,26 @@ #!/usr/bin/env bash LEO_BIN=${1} + +# This checksum comes from the reviewed, locally compiled bytecode in this fixture's expectations. +NETWORK_LOCK=$(mktemp) +trap 'rm -f "$NETWORK_LOCK"' EXIT +cat > "$NETWORK_LOCK" <<'JSON' +{"version": 2, "git": [], "network": [ + { + "name": "test_json_program.aleo", + "network": "testnet", + "edition": 0, + "checksum": [ + 99, 220, 60, 163, 190, 204, 85, 96, 196, 10, 58, 203, 48, 123, 58, 247, + 226, 60, 107, 144, 47, 213, 35, 185, 180, 177, 90, 118, 89, 106, 199, 183 + ] + } +]} +JSON PRIVATE_KEY="APrivateKey1zkp2RWGDcde3efb89rjhME1VYA8QMxcxep5DShNBR6n8Yjh" COMMON_OPTS="--disable-update-check --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" +COMMON_OPTS="--network-lock $NETWORK_LOCK $COMMON_OPTS" # `synthesize` and `query` don't sign or set consensus, so they only take the connection options. CONN_OPTS="--disable-update-check --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030}" diff --git a/tests/tests/cli/network_dependency/COMMANDS b/tests/tests/cli/network_dependency/COMMANDS index e28665b60bb..9dec5105357 100755 --- a/tests/tests/cli/network_dependency/COMMANDS +++ b/tests/tests/cli/network_dependency/COMMANDS @@ -1,8 +1,35 @@ #!/usr/bin/env bash LEO_BIN=${1} + +# These checksums come from the reviewed, locally compiled bytecode in this fixture's expectations. +NETWORK_LOCK=$(mktemp) +trap 'rm -f "$NETWORK_LOCK"' EXIT +cat > "$NETWORK_LOCK" <<'JSON' +{"version": 2, "git": [], "network": [ + { + "name": "test_program1.aleo", + "network": "testnet", + "edition": 0, + "checksum": [ + 192, 225, 38, 151, 139, 155, 148, 209, 105, 110, 246, 83, 90, 243, 78, 35, + 155, 186, 242, 17, 196, 169, 179, 144, 71, 227, 34, 207, 121, 158, 34, 25 + ] + }, + { + "name": "test_program2.aleo", + "network": "testnet", + "edition": 0, + "checksum": [ + 206, 255, 111, 48, 59, 85, 122, 166, 182, 205, 10, 141, 58, 23, 70, 232, + 105, 221, 83, 216, 186, 14, 207, 24, 13, 221, 236, 0, 143, 199, 151, 2 + ] + } +]} +JSON PRIVATE_KEY="APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH" COMMON_OPTS="-y --disable-update-check --broadcast --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" +COMMON_OPTS="--network-lock $NETWORK_LOCK $COMMON_OPTS" cd test_program1 || exit 1 echo "DEPLOYMENT 1" diff --git a/tests/tests/cli/test_deploy/COMMANDS b/tests/tests/cli/test_deploy/COMMANDS index 65e8b991378..d531c462d72 100755 --- a/tests/tests/cli/test_deploy/COMMANDS +++ b/tests/tests/cli/test_deploy/COMMANDS @@ -3,8 +3,26 @@ set -e LEO_BIN=${1} + +# This checksum comes from the reviewed, locally compiled bytecode in this fixture's expectations. +NETWORK_LOCK=$(mktemp) +trap 'rm -f "$NETWORK_LOCK"' EXIT +cat > "$NETWORK_LOCK" <<'JSON' +{"version": 2, "git": [], "network": [ + { + "name": "some_sample_leo_program.aleo", + "network": "testnet", + "edition": 0, + "checksum": [ + 218, 29, 76, 10, 129, 17, 226, 52, 243, 166, 142, 129, 90, 12, 147, 224, + 200, 219, 166, 88, 40, 193, 250, 177, 40, 42, 100, 97, 232, 64, 198, 156 + ] + } +]} +JSON PRIVATE_KEY="APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH" COMMON_OPTS="--disable-update-check --broadcast --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" +COMMON_OPTS="--network-lock $NETWORK_LOCK $COMMON_OPTS" ALEO_FILE="./build/some_sample_leo_program/some_sample_leo_program.aleo" $LEO_BIN build --disable-update-check --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} diff --git a/tests/tests/cli/test_deploy_rename/COMMANDS b/tests/tests/cli/test_deploy_rename/COMMANDS index 0106c95b27f..1643cad16b6 100755 --- a/tests/tests/cli/test_deploy_rename/COMMANDS +++ b/tests/tests/cli/test_deploy_rename/COMMANDS @@ -1,9 +1,27 @@ #!/usr/bin/env bash LEO_BIN=${1} + +# These checksums come from the reviewed, locally compiled bytecode in this fixture's expectations. +NETWORK_LOCK=$(mktemp) +trap 'rm -f "$NETWORK_LOCK"' EXIT +cat > "$NETWORK_LOCK" <<'JSON' +{"version": 2, "git": [], "network": [ + { + "name": "renamed_prog.aleo", + "network": "testnet", + "edition": 0, + "checksum": [ + 160, 166, 60, 22, 228, 140, 165, 241, 171, 140, 243, 102, 94, 227, 44, 139, + 3, 205, 179, 211, 213, 124, 14, 113, 28, 135, 160, 183, 198, 61, 226, 215 + ] + } +]} +JSON PRIVATE_KEY="APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH" ENDPOINT="http://localhost:${LEO_DEVNODE_PORT:-3030}" COMMON_OPTS="--disable-update-check --network testnet --endpoint $ENDPOINT --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" +COMMON_OPTS="--network-lock $NETWORK_LOCK $COMMON_OPTS" # The package declares `original_prog.aleo`, but `--rename` recompiles and deploys # it on-chain under a different identity. diff --git a/tests/tests/cli/test_json/COMMANDS b/tests/tests/cli/test_json/COMMANDS index 85181daf885..5617ace4f15 100755 --- a/tests/tests/cli/test_json/COMMANDS +++ b/tests/tests/cli/test_json/COMMANDS @@ -1,8 +1,26 @@ #!/usr/bin/env bash LEO_BIN=${1} + +# This checksum comes from the reviewed, locally compiled bytecode in this fixture's expectations. +NETWORK_LOCK=$(mktemp) +trap 'rm -f "$NETWORK_LOCK"' EXIT +cat > "$NETWORK_LOCK" <<'JSON' +{"version": 2, "git": [], "network": [ + { + "name": "test_json_program.aleo", + "network": "testnet", + "edition": 0, + "checksum": [ + 99, 220, 60, 163, 190, 204, 85, 96, 196, 10, 58, 203, 48, 123, 58, 247, + 226, 60, 107, 144, 47, 213, 35, 185, 180, 177, 90, 118, 89, 106, 199, 183 + ] + } +]} +JSON PRIVATE_KEY="APrivateKey1zkp2RWGDcde3efb89rjhME1VYA8QMxcxep5DShNBR6n8Yjh" COMMON_OPTS="--disable-update-check --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" +COMMON_OPTS="--network-lock $NETWORK_LOCK $COMMON_OPTS" # `synthesize` and `query` don't sign or set consensus, so they only take the connection options. CONN_OPTS="--disable-update-check --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030}" From 89e23009d80ad2c1d11f58c22ed02dace9f0d318 Mon Sep 17 00:00:00 2001 From: Mohammad Fawaz Date: Mon, 28 Sep 2026 14:58:45 -0400 Subject: [PATCH 02/10] Test rejected CLI trust pins and native credits across networks --- crates/package/src/tests.rs | 80 +++++++++++++-------- tests/expectations/cli/test_deploy/COMMANDS | 66 ++++++++++++++++- tests/expectations/cli/test_deploy/STDOUT | 1 + tests/tests/cli/test_deploy/COMMANDS | 66 ++++++++++++++++- 4 files changed, 180 insertions(+), 33 deletions(-) diff --git a/crates/package/src/tests.rs b/crates/package/src/tests.rs index 25164b84694..bec795b9db5 100644 --- a/crates/package/src/tests.rs +++ b/crates/package/src/tests.rs @@ -42,7 +42,7 @@ use crate::{ use leo_span::Symbol; -use snarkvm::prelude::{Program, TestnetV0}; +use snarkvm::prelude::{CanaryV0, MainnetV0, Program, TestnetV0}; use std::{ io::{Read, Write}, net::TcpListener, @@ -341,37 +341,55 @@ fn rejected_network_response_does_not_create_a_cache() { fn bundled_credits_ignores_hostile_cache() { leo_span::create_session_if_not_set_then(|_| { let home = unique_dir("network-bundled-credits"); - let cache = home.join("registry/testnet/credits/0/credits.aleo"); let hostile = TRUSTED_TOKEN.replace("token.aleo", "credits.aleo"); - write_file(&cache, &hostile); - let unit = crate::CompilationUnit::fetch( - Symbol::intern("credits.aleo"), - None, - &home, - leo_ast::NetworkName::TestnetV0, - "http://127.0.0.1:1", - false, - 0, - &Lock::default(), - ) - .expect("bundled credits must work without a pin or endpoint"); - let expected = Program::::credits().expect("bundled credits must exist").to_string(); - assert!(matches!(unit.data, crate::ProgramData::Bytecode(ref bytecode) if bytecode == &expected)); - assert_eq!(unit.edition, Some(0)); - assert_eq!(std::fs::read_to_string(cache).expect("hostile cache must remain unchanged"), hostile); - assert!( - crate::CompilationUnit::fetch( - Symbol::intern("credits.aleo"), - Some(1), - &home, - leo_ast::NetworkName::TestnetV0, - "http://127.0.0.1:1", - false, - 0, - &Lock::default(), - ) - .is_err() - ); + for network in + [leo_ast::NetworkName::MainnetV0, leo_ast::NetworkName::TestnetV0, leo_ast::NetworkName::CanaryV0] + { + let expected = match network { + leo_ast::NetworkName::MainnetV0 => { + Program::::credits().expect("bundled credits must exist").to_string() + } + leo_ast::NetworkName::TestnetV0 => { + Program::::credits().expect("bundled credits must exist").to_string() + } + leo_ast::NetworkName::CanaryV0 => { + Program::::credits().expect("bundled credits must exist").to_string() + } + }; + let cache = home.join(format!("registry/{network}/credits/0/credits.aleo")); + write_file(&cache, &hostile); + for no_cache in [false, true] { + for edition in [None, Some(0)] { + let unit = crate::CompilationUnit::fetch( + Symbol::intern("credits.aleo"), + edition, + &home, + network, + "http://127.0.0.1:1", + no_cache, + 0, + &Lock::default(), + ) + .expect("bundled credits must work without a pin or endpoint"); + assert!(matches!(unit.data, crate::ProgramData::Bytecode(ref bytecode) if bytecode == &expected)); + assert_eq!(unit.edition, Some(0)); + assert_eq!(std::fs::read_to_string(&cache).expect("hostile cache must remain unchanged"), hostile); + } + let error = crate::CompilationUnit::fetch( + Symbol::intern("credits.aleo"), + Some(1), + &home, + network, + "http://127.0.0.1:1", + no_cache, + 0, + &Lock::default(), + ) + .expect_err("Bundled credits must reject nonzero editions before contacting the endpoint"); + assert!(error.to_string().contains("credits must use edition zero"), "{error}"); + assert_eq!(std::fs::read_to_string(&cache).expect("hostile cache must remain unchanged"), hostile); + } + } std::fs::remove_dir_all(home).expect("test directory must be removed"); }); } diff --git a/tests/expectations/cli/test_deploy/COMMANDS b/tests/expectations/cli/test_deploy/COMMANDS index d531c462d72..c1887bce9f2 100755 --- a/tests/expectations/cli/test_deploy/COMMANDS +++ b/tests/expectations/cli/test_deploy/COMMANDS @@ -6,7 +6,8 @@ LEO_BIN=${1} # This checksum comes from the reviewed, locally compiled bytecode in this fixture's expectations. NETWORK_LOCK=$(mktemp) -trap 'rm -f "$NETWORK_LOCK"' EXIT +REJECTED=$(mktemp -d) +trap 'rm -f "$NETWORK_LOCK"; rm -rf "$REJECTED"' EXIT cat > "$NETWORK_LOCK" <<'JSON' {"version": 2, "git": [], "network": [ { @@ -27,5 +28,68 @@ ALEO_FILE="./build/some_sample_leo_program/some_sample_leo_program.aleo" $LEO_BIN build --disable-update-check --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} $LEO_BIN --path $ALEO_FILE deploy -y $COMMON_OPTS +# An explicit untrusted lock must override an otherwise trusted implicit lock. +mkdir "$REJECTED/remote" "$REJECTED/home" +cp "$NETWORK_LOCK" "$REJECTED/remote/leo.lock" +printf '{"version":2,"git":[],"network":[]}' > "$REJECTED/missing.lock" +sed 's/218, 29/0, 29/' "$NETWORK_LOCK" > "$REJECTED/mismatch.lock" +cp "$ALEO_FILE" "$REJECTED/original.aleo" +for trust in missing mismatch; do + if [ "$trust" = missing ]; then + diagnostic="no trusted checksum pin" + else + diagnostic="bytecode does not match the trusted checksum" + fi + for command in upgrade run execute synthesize; do + case "$command" in + upgrade) + args=(--path "$ALEO_FILE" upgrade --no-local --no-cache --yes --broadcast --print --save "$REJECTED/released") + ;; + run) + args=(--path "$REJECTED/remote" run some_sample_leo_program.aleo::main 1u32 2u32 --no-cache) + ;; + execute) + args=(--path "$REJECTED/remote" execute some_sample_leo_program.aleo::main 1u32 2u32 --no-cache --yes --broadcast --print --save "$REJECTED/released") + ;; + synthesize) + args=(--path "$REJECTED/remote" synthesize some_sample_leo_program.aleo --save "$REJECTED/released") + ;; + esac + if [ "$command" != synthesize ]; then + args+=(--private-key "$PRIVATE_KEY") + fi + if "$LEO_BIN" --disable-update-check --home "$REJECTED/home" --network-lock "$REJECTED/$trust.lock" "${args[@]}" \ + --network testnet --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" \ + > "$REJECTED/output" 2>&1; then + cat "$REJECTED/output" + echo "$command accepted $trust trust" >&2 + exit 1 + fi + if ! grep -Fq "$diagnostic" "$REJECTED/output"; then + cat "$REJECTED/output" + exit 1 + fi + test ! -e "$REJECTED/released" + test ! -e "$REJECTED/home/registry" + if grep -Eq 'at1[[:alnum:]]+|Broadcasting|transaction_id|Proving' "$REJECTED/output"; then + cat "$REJECTED/output" + exit 1 + fi + cmp "$ALEO_FILE" "$REJECTED/original.aleo" + cmp "$NETWORK_LOCK" "$REJECTED/remote/leo.lock" + done +done +# The rejected upgrade must leave the deployed edition unchanged. +test "$(curl -fsS "http://localhost:${LEO_DEVNODE_PORT:-3030}/testnet/program/some_sample_leo_program.aleo/latest_edition")" = 0 +# The same remote dispatch succeeds with the reviewed pin. +if ! "$LEO_BIN" --disable-update-check --path "$REJECTED/remote" --home "$REJECTED/home" --network-lock "$NETWORK_LOCK" \ + run some_sample_leo_program.aleo::main 1u32 2u32 --no-cache --network testnet \ + --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" --private-key "$PRIVATE_KEY" > "$REJECTED/output" 2>&1; then + cat "$REJECTED/output" + exit 1 +fi +grep -Fq '3u32' "$REJECTED/output" +echo "Rejected missing and mismatched pins for upgrade, run, execute, and synthesize." + $LEO_BIN --path $ALEO_FILE upgrade --no-local -y $COMMON_OPTS $LEO_BIN --path $ALEO_FILE execute -y main 1u32 2u32 $COMMON_OPTS diff --git a/tests/expectations/cli/test_deploy/STDOUT b/tests/expectations/cli/test_deploy/STDOUT index e437116d58c..1c73c50ea24 100644 --- a/tests/expectations/cli/test_deploy/STDOUT +++ b/tests/expectations/cli/test_deploy/STDOUT @@ -80,6 +80,7 @@ Once it is deployed, it CANNOT be changed. Explored XXXXXX blocks. Transaction accepted. ✅ Deployment confirmed! +Rejected missing and mismatched pins for upgrade, run, execute, and synthesize. 📢 Using the following consensus heights: 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 To override, pass in `--consensus-heights` or override the environment variable `CONSENSUS_VERSION_HEIGHTS`. diff --git a/tests/tests/cli/test_deploy/COMMANDS b/tests/tests/cli/test_deploy/COMMANDS index d531c462d72..c1887bce9f2 100755 --- a/tests/tests/cli/test_deploy/COMMANDS +++ b/tests/tests/cli/test_deploy/COMMANDS @@ -6,7 +6,8 @@ LEO_BIN=${1} # This checksum comes from the reviewed, locally compiled bytecode in this fixture's expectations. NETWORK_LOCK=$(mktemp) -trap 'rm -f "$NETWORK_LOCK"' EXIT +REJECTED=$(mktemp -d) +trap 'rm -f "$NETWORK_LOCK"; rm -rf "$REJECTED"' EXIT cat > "$NETWORK_LOCK" <<'JSON' {"version": 2, "git": [], "network": [ { @@ -27,5 +28,68 @@ ALEO_FILE="./build/some_sample_leo_program/some_sample_leo_program.aleo" $LEO_BIN build --disable-update-check --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} $LEO_BIN --path $ALEO_FILE deploy -y $COMMON_OPTS +# An explicit untrusted lock must override an otherwise trusted implicit lock. +mkdir "$REJECTED/remote" "$REJECTED/home" +cp "$NETWORK_LOCK" "$REJECTED/remote/leo.lock" +printf '{"version":2,"git":[],"network":[]}' > "$REJECTED/missing.lock" +sed 's/218, 29/0, 29/' "$NETWORK_LOCK" > "$REJECTED/mismatch.lock" +cp "$ALEO_FILE" "$REJECTED/original.aleo" +for trust in missing mismatch; do + if [ "$trust" = missing ]; then + diagnostic="no trusted checksum pin" + else + diagnostic="bytecode does not match the trusted checksum" + fi + for command in upgrade run execute synthesize; do + case "$command" in + upgrade) + args=(--path "$ALEO_FILE" upgrade --no-local --no-cache --yes --broadcast --print --save "$REJECTED/released") + ;; + run) + args=(--path "$REJECTED/remote" run some_sample_leo_program.aleo::main 1u32 2u32 --no-cache) + ;; + execute) + args=(--path "$REJECTED/remote" execute some_sample_leo_program.aleo::main 1u32 2u32 --no-cache --yes --broadcast --print --save "$REJECTED/released") + ;; + synthesize) + args=(--path "$REJECTED/remote" synthesize some_sample_leo_program.aleo --save "$REJECTED/released") + ;; + esac + if [ "$command" != synthesize ]; then + args+=(--private-key "$PRIVATE_KEY") + fi + if "$LEO_BIN" --disable-update-check --home "$REJECTED/home" --network-lock "$REJECTED/$trust.lock" "${args[@]}" \ + --network testnet --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" \ + > "$REJECTED/output" 2>&1; then + cat "$REJECTED/output" + echo "$command accepted $trust trust" >&2 + exit 1 + fi + if ! grep -Fq "$diagnostic" "$REJECTED/output"; then + cat "$REJECTED/output" + exit 1 + fi + test ! -e "$REJECTED/released" + test ! -e "$REJECTED/home/registry" + if grep -Eq 'at1[[:alnum:]]+|Broadcasting|transaction_id|Proving' "$REJECTED/output"; then + cat "$REJECTED/output" + exit 1 + fi + cmp "$ALEO_FILE" "$REJECTED/original.aleo" + cmp "$NETWORK_LOCK" "$REJECTED/remote/leo.lock" + done +done +# The rejected upgrade must leave the deployed edition unchanged. +test "$(curl -fsS "http://localhost:${LEO_DEVNODE_PORT:-3030}/testnet/program/some_sample_leo_program.aleo/latest_edition")" = 0 +# The same remote dispatch succeeds with the reviewed pin. +if ! "$LEO_BIN" --disable-update-check --path "$REJECTED/remote" --home "$REJECTED/home" --network-lock "$NETWORK_LOCK" \ + run some_sample_leo_program.aleo::main 1u32 2u32 --no-cache --network testnet \ + --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" --private-key "$PRIVATE_KEY" > "$REJECTED/output" 2>&1; then + cat "$REJECTED/output" + exit 1 +fi +grep -Fq '3u32' "$REJECTED/output" +echo "Rejected missing and mismatched pins for upgrade, run, execute, and synthesize." + $LEO_BIN --path $ALEO_FILE upgrade --no-local -y $COMMON_OPTS $LEO_BIN --path $ALEO_FILE execute -y main 1u32 2u32 $COMMON_OPTS From cd8960cd8aa64282e85abbb68dc7679dcf4a7518 Mon Sep 17 00:00:00 2001 From: Mohammad Fawaz Date: Tue, 29 Sep 2026 08:53:57 -0400 Subject: [PATCH 03/10] Simplify verified upgrade program handling --- crates/leo/src/cli/commands/upgrade.rs | 22 ++++++++++----------- tests/expectations/cli/test_deploy/COMMANDS | 4 ++-- tests/tests/cli/test_deploy/COMMANDS | 4 ++-- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/crates/leo/src/cli/commands/upgrade.rs b/crates/leo/src/cli/commands/upgrade.rs index 086758dc7a4..30960dedfbd 100644 --- a/crates/leo/src/cli/commands/upgrade.rs +++ b/crates/leo/src/cli/commands/upgrade.rs @@ -511,12 +511,12 @@ fn handle_upgrade>( Ok(build_deploy_output(config, &transactions, &all_stats, &all_broadcasts)) } -fn validate_upgrade_tasks( - verified_programs: &[(Program, u16)], +fn validate_upgrade_tasks<'a, N: Network>( + verified_programs: &'a [(Program, u16)], tasks: &[Task], skipped: &HashSet>, consensus_version: ConsensusVersion, -) -> Result, Program)>> { +) -> Result>> { let mut remote_programs = Vec::with_capacity(tasks.len()); for Task { id, program, is_local, .. } in tasks { @@ -525,14 +525,12 @@ fn validate_upgrade_tasks( continue; } - let remote_program = - verified_programs - .iter() - .find(|(remote, _)| remote.id() == id) - .map(|(remote, _)| remote) - .ok_or_else(|| crate::errors::custom(format!("Missing verified program for upgrade: {id}")))?; + let (remote_program, _) = verified_programs + .iter() + .find(|(remote, _)| remote.id() == id) + .ok_or_else(|| crate::errors::custom(format!("Missing verified program for upgrade: {id}")))?; reject_invalid_upgrade(id, remote_program, program, consensus_version)?; - remote_programs.push((*id, remote_program.clone())); + remote_programs.push(remote_program); } Ok(remote_programs) @@ -589,7 +587,7 @@ fn check_tasks_for_warnings( endpoint: &str, network: NetworkName, tasks: &[Task], - remote_programs: &[(ProgramID, Program)], + remote_programs: &[&Program], consensus_version: ConsensusVersion, command: &LeoUpgrade, ) -> Vec { @@ -600,7 +598,7 @@ fn check_tasks_for_warnings( } // Check if the program exists on the network. - if let Some((_, remote_program)) = remote_programs.iter().find(|(remote_id, _)| remote_id == id) { + if let Some(remote_program) = remote_programs.iter().find(|remote| remote.id() == id) { push_remote_upgrade_warnings(id, remote_program, program, consensus_version, &mut warnings); } // Check if the program has a valid naming scheme. diff --git a/tests/expectations/cli/test_deploy/COMMANDS b/tests/expectations/cli/test_deploy/COMMANDS index c1887bce9f2..0ddd62a30dc 100755 --- a/tests/expectations/cli/test_deploy/COMMANDS +++ b/tests/expectations/cli/test_deploy/COMMANDS @@ -5,9 +5,9 @@ set -e LEO_BIN=${1} # This checksum comes from the reviewed, locally compiled bytecode in this fixture's expectations. -NETWORK_LOCK=$(mktemp) REJECTED=$(mktemp -d) -trap 'rm -f "$NETWORK_LOCK"; rm -rf "$REJECTED"' EXIT +NETWORK_LOCK="$REJECTED/trusted.lock" +trap 'rm -rf "$REJECTED"' EXIT cat > "$NETWORK_LOCK" <<'JSON' {"version": 2, "git": [], "network": [ { diff --git a/tests/tests/cli/test_deploy/COMMANDS b/tests/tests/cli/test_deploy/COMMANDS index c1887bce9f2..0ddd62a30dc 100755 --- a/tests/tests/cli/test_deploy/COMMANDS +++ b/tests/tests/cli/test_deploy/COMMANDS @@ -5,9 +5,9 @@ set -e LEO_BIN=${1} # This checksum comes from the reviewed, locally compiled bytecode in this fixture's expectations. -NETWORK_LOCK=$(mktemp) REJECTED=$(mktemp -d) -trap 'rm -f "$NETWORK_LOCK"; rm -rf "$REJECTED"' EXIT +NETWORK_LOCK="$REJECTED/trusted.lock" +trap 'rm -rf "$REJECTED"' EXIT cat > "$NETWORK_LOCK" <<'JSON' {"version": 2, "git": [], "network": [ { From 97466b7adabe9c2b7486d57a5e7e67e200b82cae Mon Sep 17 00:00:00 2001 From: Mohammad Fawaz Date: Wed, 30 Sep 2026 10:54:11 -0400 Subject: [PATCH 04/10] Automatically lock network dependency editions and checksums --- crates/leo/src/cli/cli.rs | 505 ++++++++---------- crates/leo/src/cli/commands/add.rs | 47 +- crates/leo/src/cli/commands/build.rs | 6 +- crates/leo/src/cli/commands/common/options.rs | 2 +- crates/leo/src/cli/commands/common/query.rs | 11 +- crates/leo/src/cli/commands/common/util.rs | 9 +- crates/leo/src/cli/commands/deploy.rs | 3 +- crates/leo/src/cli/commands/execute.rs | 6 +- crates/leo/src/cli/commands/run.rs | 6 +- crates/leo/src/cli/commands/synthesize.rs | 3 +- crates/leo/src/cli/commands/upgrade.rs | 32 +- crates/leo/src/cli/helpers/context.rs | 32 +- crates/package/README.md | 42 +- crates/package/src/compilation_unit.rs | 45 +- crates/package/src/errors.rs | 4 +- crates/package/src/git.rs | 2 +- crates/package/src/lock.rs | 80 ++- crates/package/src/package.rs | 118 +--- crates/package/src/tests.rs | 425 +++++++++++---- .../cli/network_dependency/COMMANDS | 41 +- .../cli/test_add_dependency/STDERR | 4 +- tests/expectations/cli/test_deploy/COMMANDS | 143 +++-- tests/expectations/cli/test_deploy/STDOUT | 3 +- .../cli/test_deploy_rename/COMMANDS | 17 - tests/expectations/cli/test_json/COMMANDS | 17 - tests/tests/cli/network_dependency/COMMANDS | 41 +- tests/tests/cli/test_deploy/COMMANDS | 143 +++-- tests/tests/cli/test_deploy_rename/COMMANDS | 17 - tests/tests/cli/test_json/COMMANDS | 17 - 29 files changed, 930 insertions(+), 891 deletions(-) diff --git a/crates/leo/src/cli/cli.rs b/crates/leo/src/cli/cli.rs index 5cb8f17fecc..51adba39680 100644 --- a/crates/leo/src/cli/cli.rs +++ b/crates/leo/src/cli/cli.rs @@ -49,9 +49,6 @@ pub struct CLI { #[clap(long, global = true, help = "Path to aleo program registry")] pub home: Option, - #[clap(long, global = true, help = "Read trusted network program pins from this lock file")] - network_lock: Option, - #[clap(short = 'p', long = "package", global = true, help = "Target a specific workspace member by name")] pub package: Option, } @@ -239,9 +236,7 @@ pub fn run_with_args(cli: CLI) -> Result<()> { // Get custom root folder and create context for it. // If not specified, default context will be created in cwd. - let mut context = handle_error(Context::new(cli.path.clone(), cli.home, false, cli.package.clone())); - context.network_lock = - cli.network_lock.map(std::path::absolute).transpose().map_err(crate::errors::cli_io_error)?; + let context = handle_error(Context::new(cli.path.clone(), cli.home, false, cli.package.clone())); let command_name = cli.command.name(); let mut command_output: Option = None; @@ -340,100 +335,6 @@ mod tests { use serial_test::serial; use std::{env::temp_dir, path::PathBuf}; - #[test] - fn network_lock_is_global_and_survives_member_context() { - use crate::cli::context::Context; - - for args in [vec!["leo", "--network-lock", "trusted.lock", "run", "example.aleo/main"], vec![ - "leo", - "run", - "example.aleo/main", - "--network-lock", - "trusted.lock", - ]] { - let cli = CLI::try_parse_from(args).expect("The global lock option must parse"); - assert_eq!(cli.network_lock, Some(PathBuf::from("trusted.lock"))); - let mut context = Context::new(None, None, false, None).expect("The context must be valid"); - context.network_lock = - cli.network_lock.map(std::path::absolute).transpose().expect("The lock path must resolve"); - assert_eq!(context.with_path(PathBuf::from("member")).network_lock, context.network_lock); - } - } - - #[test] - fn remote_program_requires_a_trusted_pin_before_fetch() { - use crate::cli::{context::Context, load_pinned_programs_from_network}; - use snarkvm::prelude::{ProgramID, TestnetV0}; - - let root = temp_dir().join(format!("leo_remote_pin_{}", std::process::id())); - std::fs::create_dir_all(&root).expect("The temporary directory must be created"); - let context = - Context::new(Some(root.clone()), Some(root.join("cache")), false, None).expect("The context must be valid"); - create_session_if_not_set_then(|_| { - let program = "unpinned.aleo".parse::>().expect("The program ID must parse"); - let error = - load_pinned_programs_from_network(&context, program, NetworkName::TestnetV0, "http://localhost:1", 0) - .expect_err("An unpinned remote program must fail"); - assert!(error.to_string().contains("trusted checksum pin"), "{error}"); - }); - assert!(!root.join("cache").exists(), "A missing pin must not create the cache"); - std::fs::remove_dir_all(root).expect("The temporary directory must be removed"); - } - - #[test] - fn network_lock_missing_override_does_not_use_project_pins() { - use crate::cli::context::Context; - - let root = temp_dir().join(format!("leo_pin_override_{}", std::process::id())); - std::fs::create_dir_all(&root).expect("The temporary directory must be created"); - std::fs::write(root.join("leo.lock"), r#"{"version":1,"git":[]}"#).expect("The project lock must be written"); - let mut context = Context::new(Some(root.clone()), None, false, None).expect("The context must be valid"); - context.network_lock = Some(root.join("absent.lock")); - assert!(context.network_pins().is_err(), "An explicit missing lock must fail"); - std::fs::remove_dir_all(root).expect("The temporary directory must be removed"); - } - - #[test] - fn upgrade_yes_and_no_cache_do_not_allow_unpinned_bytecode_imports() { - use crate::cli::{Command, context::Context}; - - let root = temp_dir().join(format!("leo_upgrade_pin_{}", std::process::id())); - std::fs::create_dir_all(root.join("cache")).expect("The temporary directory must be created"); - let source = root.join("local.aleo"); - std::fs::write( - &source, - r"import unpinned.aleo; -program local.aleo; -function main: - input r0 as u32.public; - output r0 as u32.public; -", - ) - .expect("The local program must be written"); - let cli = CLI::try_parse_from([ - "leo", - "upgrade", - "--yes", - "--no-cache", - "--network", - "testnet", - "--endpoint", - "http://localhost:1", - "--network-retries", - "0", - ]) - .expect("The upgrade options must parse"); - let Commands::Upgrade { command } = cli.command else { panic!("Expected upgrade command") }; - let context = - Context::new(Some(source), Some(root.join("cache")), false, None).expect("The context must be valid"); - create_session_if_not_set_then(|_| { - let error = command.prelude(context).expect_err("The remote import must require a pin"); - assert!(error.to_string().contains("trusted checksum pin"), "{error}"); - }); - assert!(!root.join("cache/registry").exists()); - std::fs::remove_dir_all(root).expect("The temporary directory must be removed"); - } - #[test] fn network_pins_use_the_workspace_root() { use crate::cli::context::Context; @@ -450,77 +351,38 @@ function main: let context = Context::new(Some(root.join("member")), None, false, None).expect("The context must be valid"); let pins = context.network_pins().expect("The workspace lock must load"); assert_eq!( - pins.network_pin("trusted.aleo", NetworkName::TestnetV0, None) - .expect("The workspace pin must exist") - .edition, + pins.network_pin("trusted.aleo", NetworkName::TestnetV0, None).expect("The pin must exist").edition, 7 ); std::fs::remove_dir_all(root).expect("The temporary directory must be removed"); } #[test] - fn network_lock_override_verifies_import_without_changing_project_pins() { - use leo_package::Package; - use snarkvm::prelude::{Program, TestnetV0}; + fn network_pins_find_nested_project_files_and_ignore_unrelated_locks() { + use crate::cli::context::Context; - let root = temp_dir().join(format!("leo_override_import_{}", std::process::id())); - test_helpers::scaffold_minimal_member(&root, "consumer"); - let project = root.join("consumer"); - let manifest_path = project.join("program.json"); - let mut manifest = leo_package::Manifest::read_from_file(&manifest_path).expect("The manifest must load"); - manifest.dependencies = Some(vec![leo_package::Dependency { - name: "trusted.aleo".into(), - location: leo_package::Location::Network, - ..Default::default() - }]); - manifest.write_to_file(&manifest_path).expect("The manifest must be written"); - let source = - "program trusted.aleo;\nfunction main:\n input r0 as u32.public;\n output r0 as u32.public;\n"; - let program: Program = source.parse().expect("The program must parse"); - let checksum = program.to_checksum().map(|byte| *byte); - let old_lock = serde_json::json!({"version":2,"network":[{ - "name":"trusted.aleo","network":"testnet","edition":2,"checksum":vec![0;32] + let root = temp_dir().join(format!("leo_nested_pins_{}", std::process::id())); + let file = root.join("build/program/main.aleo"); + std::fs::create_dir_all(file.parent().expect("The file has a parent")).expect("Create the build directory"); + std::fs::write(&file, "program main.aleo;").expect("Write the standalone program"); + let lock = serde_json::json!({"version":2,"network":[{ + "name":"trusted.aleo","network":"testnet","edition":7,"checksum":vec![0;32] }]}); - std::fs::write(project.join("leo.lock"), old_lock.to_string()).expect("The project lock must be written"); - let override_lock = serde_json::json!({"version":2,"network":[{ - "name":"trusted.aleo","network":"testnet","edition":4,"checksum":checksum - }]}) - .to_string(); - let lock_path = root.join("trusted.lock"); - std::fs::write(&lock_path, &override_lock).expect("The override lock must be written"); - let home = root.join("cache"); - let cache = home.join("registry/testnet/trusted/4"); - std::fs::create_dir_all(&cache).expect("The cache directory must be created"); - std::fs::write(cache.join("trusted.aleo"), source).expect("The verified cache fixture must be written"); - create_session_if_not_set_then(|_| { - let package = Package::from_directory_with_network_lock( - &project, - &home, - false, - false, - false, - Some(NetworkName::TestnetV0), - Some("http://localhost:1"), - 0, - Some(&lock_path), - ) - .expect("The trusted override must resolve the cached import"); - assert!(package.compilation_units.iter().any(|unit| unit.edition == Some(4))); - }); - let pins = leo_package::Lock::read(&project).expect("The project lock must remain valid"); + std::fs::write(root.join("leo.lock"), lock.to_string()).expect("Write the project lock"); + let context = Context::new(Some(file), None, false, None).expect("The context must be valid"); + assert!(context.network_pins().expect("Ignore locks outside projects").is_empty()); + std::fs::write(root.join("program.json"), "{}").expect("Write the project manifest"); + let pins = context.network_pins().expect("Read the enclosing project lock"); assert_eq!( - pins.network_pin("trusted.aleo", NetworkName::TestnetV0, None) - .expect("The original project pin must remain") - .edition, - 2 + pins.network_pin("trusted.aleo", NetworkName::TestnetV0, None).expect("The pin must exist").edition, + 7 ); - assert_eq!(std::fs::read_to_string(lock_path).expect("The override must remain readable"), override_lock); - std::fs::remove_dir_all(root).expect("The temporary directory must be removed"); + std::fs::remove_dir_all(root).expect("Remove the temporary directory"); } #[test] - fn remote_loader_requires_pins_for_transitive_imports() { - use crate::cli::{context::Context, load_pinned_programs_from_network}; + fn remote_loader_resolves_without_manual_pins_and_checks_existing_pins() { + use crate::cli::{context::Context, load_programs_from_network}; use snarkvm::prelude::{Program, ProgramID, TestnetV0}; use std::{ io::{Read, Write}, @@ -528,34 +390,66 @@ function main: time::{Duration, Instant}, }; - let root_source = r"import imported.aleo; -program trusted.aleo; -function main: - input r0 as u32.public; - output r0 as u32.public; -"; + let root_source = "import imported.aleo;\nprogram target.aleo;\nfunction main:\n input r0 as u32.public;\n output r0 as u32.public;\n"; let import_source = "program imported.aleo;\nfunction main:\n input r0 as u32.public;\n output r0 as u32.public;\n"; - for pin_import in [false, true] { - let root = temp_dir().join(format!("leo_remote_import_pins_{}_{pin_import}", std::process::id())); - std::fs::create_dir_all(&root).expect("The temporary directory must be created"); + for case in ["unlocked", "locked", "upgrade", "root_mismatch", "import_mismatch"] { + let root = temp_dir().join(format!("leo_auto_remote_{}_{case}", std::process::id())); + std::fs::create_dir_all(&root).expect("The fixture directory must exist"); + let home = root.join("cache"); + let cache = home.join("registry/testnet/target/7/target.aleo"); + std::fs::create_dir_all(cache.parent().expect("The cache file has a parent")).expect("Create cache"); + std::fs::write(&cache, if case == "unlocked" { "unverified cache" } else { root_source }) + .expect("Seed cache"); let mut pins = Vec::new(); - for source in [root_source, import_source].into_iter().take(if pin_import { 2 } else { 1 }) { - let program: Program = source.parse().expect("The fixture must parse"); - pins.push(serde_json::json!({"name":program.id().to_string(), "network":"testnet", "edition":3, - "checksum":program.to_checksum().map(|byte| *byte)})); + if case != "unlocked" { + test_helpers::scaffold_minimal_member(&root, "consumer"); + for (source, edition) in [(root_source, if case == "upgrade" { 2 } else { 7 }), (import_source, 3)] { + let program: Program = source.parse().expect("The fixture must parse"); + pins.push( + serde_json::json!({"name":program.id().to_string(),"network":"testnet","edition":edition, + "checksum":program.to_checksum().map(|byte| *byte)}), + ); + } + } + let project = if case == "unlocked" { root.clone() } else { root.join("consumer") }; + let lock_path = project.join("leo.lock"); + let original_lock = serde_json::json!({"version":2,"network":pins}).to_string(); + if case != "unlocked" { + std::fs::write(&lock_path, &original_lock).expect("Write project lock"); + } + let mut responses = Vec::new(); + if case == "unlocked" { + responses.push(("/testnet/program/target.aleo/latest_edition", "7".to_string())); } - let lock_path = root.join("trusted.lock"); - std::fs::write(&lock_path, serde_json::json!({"version":2,"network":pins}).to_string()) - .expect("The trusted pins must be written"); - let listener = TcpListener::bind("127.0.0.1:0").expect("The local server must bind"); - listener.set_nonblocking(true).expect("The listener must support a timeout"); - let endpoint = format!("http://{}", listener.local_addr().expect("The local address must exist")); + responses.push(( + "/testnet/program/target.aleo/7", + serde_json::to_string(&if case == "root_mismatch" { + root_source.replace("public", "private") + } else { + root_source.to_string() + }) + .expect("Serialize root"), + )); + if case != "root_mismatch" { + if case == "unlocked" { + responses.push(("/testnet/program/imported.aleo/latest_edition", "3".to_string())); + } + responses.push(( + "/testnet/program/imported.aleo/3", + serde_json::to_string(&if case == "import_mismatch" { + import_source.replace("public", "private") + } else { + import_source.to_string() + }) + .expect("Serialize import"), + )); + } + let listener = TcpListener::bind("127.0.0.1:0").expect("Bind fixture"); + listener.set_nonblocking(true).expect("Set accept mode"); + let endpoint = format!("http://{}", listener.local_addr().expect("Read fixture address")); let server = std::thread::spawn(move || { - for (name, source) in [("trusted", root_source), ("imported", import_source)] - .into_iter() - .take(if pin_import { 2 } else { 1 }) - { + for (path, body) in responses { let start = Instant::now(); let mut stream = loop { match listener.accept() { @@ -564,52 +458,165 @@ function main: if error.kind() == std::io::ErrorKind::WouldBlock && start.elapsed() < Duration::from_secs(10) => { - std::thread::sleep(Duration::from_millis(5)); + std::thread::sleep(Duration::from_millis(5)) } Err(error) => panic!("Expected a program request: {error}"), } }; - stream.set_nonblocking(false).expect("The accepted stream must block"); - stream.set_read_timeout(Some(Duration::from_secs(10))).expect("The read timeout must be set"); + stream.set_nonblocking(false).expect("Set stream mode"); + stream.set_read_timeout(Some(Duration::from_secs(10))).expect("Set read timeout"); let mut request = [0; 4096]; - let count = stream.read(&mut request).expect("The request must arrive"); - assert!( - String::from_utf8_lossy(&request[..count]).contains(&format!("/testnet/program/{name}.aleo/3")) - ); - let body = serde_json::to_string(source).expect("The program must serialize"); + let count = stream.read(&mut request).expect("Read request"); + assert!(String::from_utf8_lossy(&request[..count]).starts_with(&format!("GET {path} HTTP/1.1"))); write!( stream, "HTTP/1.1 200 OK\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}", body.len() ) - .expect("The response must be written"); + .expect("Write response"); } }); - let mut context = Context::new(Some(root.clone()), Some(root.join("cache")), false, None) - .expect("The context must be valid"); - context.network_lock = Some(lock_path); + let context = Context::new(Some(project), Some(home), false, None).expect("Create context"); create_session_if_not_set_then(|_| { - let id: ProgramID = "trusted.aleo".parse().expect("The ID must parse"); - let result = load_pinned_programs_from_network(&context, id, NetworkName::TestnetV0, &endpoint, 0); - if pin_import { - let programs = result.expect("Both trusted programs must load"); + let id = "target.aleo".parse::>().expect("Parse ID"); + let result = load_programs_from_network( + &context, + id, + NetworkName::TestnetV0, + &endpoint, + 0, + (case == "upgrade").then_some(7), + ); + if case.ends_with("mismatch") { + assert!(result.expect_err("Changed bytes must fail").to_string().contains("checksum")); + } else { + let programs = result.expect("No manual pins must be required"); assert_eq!(programs.len(), 2); assert_eq!(programs[0].0.id().to_string(), "imported.aleo"); - assert!(programs.iter().all(|(_, edition)| *edition == Some(3))); - } else { - let error = result.expect_err("An unpinned transitive import must fail"); - assert!(error.to_string().contains("trusted checksum pin"), "{error}"); - assert!(!root.join("cache/registry/testnet/imported").exists()); + assert_eq!(programs[0].1, Some(3)); + assert_eq!(programs[1].1, Some(7)); } }); server.join().expect("The server must finish"); - std::fs::remove_dir_all(root).expect("The temporary directory must be removed"); + if case == "unlocked" { + assert!(!lock_path.exists(), "An ad-hoc command must not create a project lock"); + } else { + assert_eq!(std::fs::read_to_string(lock_path).expect("Read unchanged lock"), original_lock); + } + assert_eq!(std::fs::read_to_string(cache).expect("Read root cache"), root_source); + std::fs::remove_dir_all(root).expect("Remove fixture"); + } + } + + #[cfg(unix)] + #[test] + #[serial] + fn add_network_dependency_restores_lock_when_manifest_write_fails() { + use leo_package::{Dependency, Location, Lock, Manifest}; + use snarkvm::prelude::{Program, TestnetV0}; + use std::{ + io::{Read, Write}, + net::TcpListener, + os::unix::fs::PermissionsExt, + time::{Duration, Instant}, + }; + + let source = + "program target.aleo;\nfunction main:\n input r0 as u32.public;\n output r0 as u32.public;\n"; + let program: Program = source.parse().expect("The fixture must parse"); + dotenvy::dotenv().ok(); + let network = crate::cli::get_network(&None).unwrap_or(NetworkName::TestnetV0); + for existing_lock in [false, true] { + let root = tempfile::tempdir().expect("Create the fixture directory"); + test_helpers::scaffold_minimal_member(root.path(), "consumer"); + let project = root.path().join("consumer"); + let manifest_path = project.join("program.json"); + let mut manifest = Manifest::read_from_file(&manifest_path).expect("Read the fixture manifest"); + manifest.dependencies = Some(vec![Dependency { + name: "target.aleo".to_owned(), + location: Location::Network, + ..Default::default() + }]); + manifest.write_to_file(&manifest_path).expect("Write the existing dependency"); + let original_manifest = std::fs::read(&manifest_path).expect("Read the original manifest"); + if existing_lock { + let lock = serde_json::json!({"version":2,"network":[{ + "name":"target.aleo","network":network.to_string(),"edition":0, + "checksum":program.to_checksum().map(|byte| *byte) + }]}); + std::fs::write(project.join("leo.lock"), lock.to_string()).expect("Write the original lock"); + } + std::fs::set_permissions(&manifest_path, std::fs::Permissions::from_mode(0o444)) + .expect("Make the manifest read-only"); + + let listener = TcpListener::bind("127.0.0.1:0").expect("Bind the fixture"); + listener.set_nonblocking(true).expect("Set accept mode"); + let endpoint = format!("http://{}", listener.local_addr().expect("Read the fixture address")); + let server = std::thread::spawn(move || { + let start = Instant::now(); + let mut stream = loop { + match listener.accept() { + Ok((stream, _)) => break stream, + Err(error) + if error.kind() == std::io::ErrorKind::WouldBlock + && start.elapsed() < Duration::from_secs(10) => + { + std::thread::sleep(Duration::from_millis(5)); + } + Err(error) => panic!("Expected a program request: {error}"), + } + }; + stream.set_nonblocking(false).expect("Set stream mode"); + stream.set_read_timeout(Some(Duration::from_secs(10))).expect("Set read timeout"); + let mut request = [0; 4096]; + let count = stream.read(&mut request).expect("Read the request"); + assert!( + String::from_utf8_lossy(&request[..count]) + .starts_with(&format!("GET /{network}/program/target.aleo/1 HTTP/1.1")) + ); + let body = serde_json::to_string(source).expect("Serialize the program"); + write!(stream, "HTTP/1.1 200 OK\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}", body.len()) + .expect("Write the response"); + }); + let cli = CLI::try_parse_from([ + "leo", + "-q", + "--disable-update-check", + "--path", + project.to_str().expect("The project path must be UTF-8"), + "--home", + root.path().to_str().expect("The home path must be UTF-8"), + "add", + "target.aleo", + "--edition", + "1", + "--endpoint", + &endpoint, + "--network-retries", + "0", + ]) + .expect("The add arguments must parse"); + create_session_if_not_set_then(|_| { + let error = run_with_args(cli).expect_err("The read-only manifest must reject the update"); + assert!(error.to_string().contains("failed to write manifest file"), "{error}"); + }); + server.join().expect("The fixture must finish"); + assert_eq!(std::fs::read(&manifest_path).expect("Read the unchanged manifest"), original_manifest); + let lock = Lock::read(&project).expect("Read the restored lock"); + if existing_lock { + let pin = lock.network_pin("target.aleo", network, None).expect("The original pin must remain"); + assert_eq!(pin.edition, 0); + pin.verify(source).expect("The original checksum must remain"); + } else { + assert!(lock.is_empty()); + assert!(!project.join("leo.lock").exists()); + } } } #[test] #[serial] - fn add_network_dependency_rejects_missing_pin() { + fn add_network_dependency_rejects_unreachable_endpoint() { let temp_dir = temp_dir(); let project_directory = temp_dir.join("add_missing_network_dep"); if project_directory.exists() { @@ -627,7 +634,6 @@ function main: path: Some(project_directory.clone()), home: None, package: None, - network_lock: None, }; let add = CLI { @@ -648,14 +654,13 @@ function main: path: Some(project_directory.clone()), home: Some(temp_dir.join(".aleo_add_missing")), package: None, - network_lock: None, }; create_session_if_not_set_then(|_| { run_with_args(new).expect("Failed to execute `leo new`"); - let error = run_with_args(add).expect_err("An unpinned network dependency must fail"); - assert!(error.to_string().contains("trusted checksum pin"), "{error}"); + let error = run_with_args(add).expect_err("An unreachable dependency must fail"); + assert!(error.to_string().contains("failed to retrieve"), "{error}"); let manifest_path = project_directory.join(leo_package::MANIFEST_FILENAME); let manifest = leo_package::Manifest::read_from_file(&manifest_path).unwrap(); @@ -703,7 +708,6 @@ function main: path: Some(project_directory.clone()), home: Some(temp_dir.join(".aleo")), package: None, - network_lock: None, }; create_session_if_not_set_then(|_| { @@ -755,7 +759,6 @@ function main: path: Some(project_directory.clone()), home: None, package: None, - network_lock: None, }; create_session_if_not_set_then(|_| { @@ -802,7 +805,6 @@ function main: path: Some(project_directory.clone()), home: None, package: None, - network_lock: None, }; create_session_if_not_set_then(|_| { @@ -846,7 +848,6 @@ function main: path: Some(project_directory.clone()), home: None, package: None, - network_lock: None, }; create_session_if_not_set_then(|_| { @@ -878,7 +879,6 @@ function main: path: Some(lib_directory.clone()), home: None, package: None, - network_lock: None, }; create_session_if_not_set_then(|_| { @@ -925,7 +925,6 @@ function main: path: Some(ws_directory.clone()), home: None, package: None, - network_lock: None, }; create_session_if_not_set_then(|_| { @@ -1085,7 +1084,6 @@ function main: path: Some(pkg_dir.clone()), home: None, package: None, - network_lock: None, }; create_session_if_not_set_then(|_| { @@ -1128,7 +1126,6 @@ function main: path: Some(pkg_dir.clone()), home: None, package: None, - network_lock: None, }; create_session_if_not_set_then(|_| { @@ -1168,7 +1165,6 @@ function main: path: Some(ws_root.clone()), home: None, package: None, - network_lock: None, }; create_session_if_not_set_then(|_| { @@ -1205,7 +1201,6 @@ function main: path: Some(ws_root.join("token")), home: None, package: None, - network_lock: None, }; create_session_if_not_set_then(|_| { @@ -1242,7 +1237,6 @@ function main: path: Some(ws_root.clone()), home: None, package: Some("token".to_string()), - network_lock: None, }; create_session_if_not_set_then(|_| { @@ -1276,7 +1270,6 @@ function main: path: Some(ws_root.clone()), home: None, package: Some("nonexistent".to_string()), - network_lock: None, }; create_session_if_not_set_then(|_| { @@ -1312,7 +1305,6 @@ function main: path: Some(ws_root.clone()), home: None, package: None, - network_lock: None, }; create_session_if_not_set_then(|_| { @@ -1332,7 +1324,6 @@ function main: path: Some(ws_root.clone()), home: None, package: None, - network_lock: None, }; create_session_if_not_set_then(|_| { @@ -1368,7 +1359,6 @@ function main: path: Some(ws_root.clone()), home: None, package: None, - network_lock: None, }; create_session_if_not_set_then(|_| { @@ -1406,7 +1396,6 @@ function main: path: Some(ws_root.join("swap")), home: None, package: None, - network_lock: None, }; create_session_if_not_set_then(|_| { @@ -1443,7 +1432,6 @@ function main: path: Some(ws_root.join("swap")), home: None, package: None, - network_lock: None, }; create_session_if_not_set_then(|_| { @@ -1496,7 +1484,6 @@ function main: path: Some(ws_root.clone()), home: None, package: None, - network_lock: None, }; create_session_if_not_set_then(|_| { @@ -1578,7 +1565,6 @@ function main: home: None, // Filter to just token. package: Some("token".to_string()), - network_lock: None, }; create_session_if_not_set_then(|_| { @@ -1632,7 +1618,6 @@ function main: path: Some(ws_root.clone()), home: None, package: None, - network_lock: None, }; create_session_if_not_set_then(|_| { @@ -1688,7 +1673,6 @@ function main: path: Some(ws_root.clone()), home: None, package: None, - network_lock: None, }; create_session_if_not_set_then(|_| { @@ -1726,7 +1710,6 @@ function main: path: Some(pkg_dir.clone()), home: None, package: None, - network_lock: None, }; create_session_if_not_set_then(|_| { @@ -1751,7 +1734,6 @@ function main: path: Some(pkg_dir.clone()), home: None, package: None, - network_lock: None, }; create_session_if_not_set_then(|_| { @@ -2242,7 +2224,6 @@ program app.aleo { path: Some(project_directory.clone()), home: None, package: None, - network_lock: None, }; create_session_if_not_set_then(|_| { @@ -2303,8 +2284,7 @@ function external_nested_function: // Overwrite `src/main.leo` file std::fs::write(project_directory.join("src").join("main.leo"), program_str).unwrap(); - // Cache the program before the add: `leo add --network` verifies existence by fetching, - // and a cached copy satisfies that check offline. + // Seed the locked dependency graph for the cached execution test. let registry = temp_dir.join(".aleo").join("registry").join("testnet"); std::fs::create_dir_all(®istry).unwrap(); @@ -2337,37 +2317,16 @@ function external_nested_function: ) .expect("The fixture pins must be written"); - // Add dependencies - let add = CLI { - debug: false, - quiet: false, - json_output: None, - disable_update_check: false, - command: Commands::Add { - command: LeoAdd { - name: "nested_example_layer_0".to_string(), - source: DependencySource { - local: None, - network: true, - edition: Some(0), - workspace: false, - git: None, - }, - git_ref: GitRef { branch: None, tag: None, rev: None }, - endpoint: None, - network_retries: 2, - dev: false, - }, - }, - path: Some(project_directory.clone()), - home: Some(temp_dir.join(".aleo")), - package: None, - network_lock: None, - }; - - create_session_if_not_set_then(|_| { - run_with_args(add).expect("Failed to execute `leo add`"); - }); + let manifest_path = project_directory.join(leo_package::MANIFEST_FILENAME); + let mut manifest = + leo_package::Manifest::read_from_file(&manifest_path).expect("The fixture manifest must load"); + manifest.dependencies = Some(vec![leo_package::Dependency { + name: "nested_example_layer_0.aleo".to_string(), + location: leo_package::Location::Network, + edition: Some(0), + ..Default::default() + }]); + manifest.write_to_file(manifest_path).expect("The fixture dependency must be written"); } pub(crate) fn sample_grandparent_package(temp_dir: &Path) { @@ -2391,7 +2350,6 @@ function external_nested_function: path: Some(grandparent_directory.clone()), home: None, package: None, - network_lock: None, }; let create_parent_project = CLI { @@ -2403,7 +2361,6 @@ function external_nested_function: path: Some(parent_directory.clone()), home: None, package: None, - network_lock: None, }; let create_child_project = CLI { @@ -2415,7 +2372,6 @@ function external_nested_function: path: Some(child_directory.clone()), home: None, package: None, - network_lock: None, }; // Add source files `grandparent/src/main.leo`, `grandparent/parent/src/main.leo`, and `grandparent/parent/child/src/main.leo` @@ -2484,7 +2440,6 @@ program child.aleo { path: Some(grandparent_directory.clone()), home: None, package: None, - network_lock: None, }; let add_grandparent_dependency_2 = CLI { @@ -2511,7 +2466,6 @@ program child.aleo { path: Some(grandparent_directory.clone()), home: None, package: None, - network_lock: None, }; let add_parent_dependency = CLI { @@ -2538,7 +2492,6 @@ program child.aleo { path: Some(parent_directory.clone()), home: None, package: None, - network_lock: None, }; // Execute all commands @@ -2579,7 +2532,6 @@ program child.aleo { path: Some(outer_directory.clone()), home: None, package: None, - network_lock: None, }; let create_inner_1_project = CLI { @@ -2593,7 +2545,6 @@ program child.aleo { path: Some(inner_1_directory.clone()), home: None, package: None, - network_lock: None, }; let create_inner_2_project = CLI { @@ -2607,7 +2558,6 @@ program child.aleo { path: Some(inner_2_directory.clone()), home: None, package: None, - network_lock: None, }; // Add source files `outer/src/main.leo` and `outer/inner/src/main.leo` @@ -2699,7 +2649,6 @@ program inner_2.aleo { path: Some(outer_directory.clone()), home: None, package: None, - network_lock: None, }; let add_outer_dependency_2 = CLI { @@ -2726,7 +2675,6 @@ program inner_2.aleo { path: Some(outer_directory.clone()), home: None, package: None, - network_lock: None, }; // Execute all commands @@ -2768,7 +2716,6 @@ program inner_2.aleo { path: Some(outer_directory.clone()), home: None, package: None, - network_lock: None, }; let create_inner_1_project = CLI { @@ -2782,7 +2729,6 @@ program inner_2.aleo { path: Some(inner_1_directory.clone()), home: None, package: None, - network_lock: None, }; let create_inner_2_project = CLI { @@ -2796,7 +2742,6 @@ program inner_2.aleo { path: Some(inner_2_directory.clone()), home: None, package: None, - network_lock: None, }; // Add source files `outer_2/src/main.leo` and `outer_2/inner/src/main.leo` @@ -2922,7 +2867,6 @@ program inner_2.aleo { path: Some(outer_directory.clone()), home: None, package: None, - network_lock: None, }; let add_outer_dependency_2 = CLI { @@ -2949,7 +2893,6 @@ program inner_2.aleo { path: Some(outer_directory.clone()), home: None, package: None, - network_lock: None, }; // Execute all commands diff --git a/crates/leo/src/cli/commands/add.rs b/crates/leo/src/cli/commands/add.rs index 45bc0a25b4c..491bf285c73 100755 --- a/crates/leo/src/cli/commands/add.rs +++ b/crates/leo/src/cli/commands/add.rs @@ -177,6 +177,7 @@ impl Command for LeoAdd { let mut manifest = Manifest::read_from_file(&manifest_path)?; let current_is_library = !manifest.program.ends_with(".aleo"); + let mut network_lock = None; // Determine dependency name, location, and path. let (name, location, dep_path) = if let Some(local_path) = &self.source.local { @@ -249,22 +250,36 @@ impl Command for LeoAdd { DEFAULT_ENDPOINT.to_string() }); let home = context.home()?; - CompilationUnit::fetch( - Symbol::intern(&name), - self.source.edition, - &home, - network, - &endpoint, - false, - self.network_retries, - &context.network_pins()?, - ) + let lock_dir = Workspace::discover_root(&path)?.unwrap_or_else(|| path.clone()); + let old_lock = Lock::read(&lock_dir)?; + let mut lock = old_lock.clone(); + (|| { + let edition = if name == "credits.aleo" { + self.source.edition.or(Some(0)) + } else { + Some(match self.source.edition { + Some(edition) => edition, + None => leo_package::fetch_latest_edition(&name, &endpoint, network, self.network_retries)?, + }) + }; + CompilationUnit::fetch( + Symbol::intern(&name), + edition, + &home, + network, + &endpoint, + true, + self.network_retries, + &mut lock, + ) + })() .map_err(|err| { crate::errors::custom(format!( "Could not find program `{name}` on network `{network}` at `{endpoint}`: {err}", )) })?; + network_lock = Some((lock_dir, lock, old_lock)); (name, Location::Network, None) }; @@ -313,7 +328,17 @@ impl Command for LeoAdd { } } - manifest.write_to_file(manifest_path)?; + if let Some((lock_dir, lock, _)) = &mut network_lock { + lock.write(lock_dir)?; + } + if let Err(error) = manifest.write_to_file(manifest_path) { + if let Some((lock_dir, _, mut old_lock)) = network_lock { + old_lock.write(&lock_dir).map_err(|restore_error| { + crate::errors::custom(format!("{error}; could not restore the dependency lock: {restore_error}")) + })?; + } + return Err(error.into()); + } Ok(()) } diff --git a/crates/leo/src/cli/commands/build.rs b/crates/leo/src/cli/commands/build.rs index d8dc7a2f19e..f9de4eb9285 100644 --- a/crates/leo/src/cli/commands/build.rs +++ b/crates/leo/src/cli/commands/build.rs @@ -127,7 +127,7 @@ fn handle_build(command: &LeoBuild, context: Context) -> Result< Result< Result<( } } -/// Load the trusted editions of a program and its imports with an iterative DFS. -pub fn load_pinned_programs_from_network( +/// Load a remote program and its imports, checking existing project dependency pins. +pub fn load_programs_from_network( context: &Context, program_id: ProgramID, network: NetworkName, endpoint: &str, network_retries: u32, + edition: Option, ) -> Result, Option)>> { use snarkvm::prelude::Program; use std::collections::HashSet; - let network_pins = context.network_pins()?; + let mut network_pins = context.network_pins()?; // A cache for loaded programs, mapping a program ID to its bytecode and edition. let mut programs = HashMap::new(); @@ -173,13 +174,13 @@ pub fn load_pinned_programs_from_network( // Fetch the program source from the network. let program = leo_package::CompilationUnit::fetch( Symbol::intern(¤t_id.name().to_string()), - None, + if current_id == program_id { edition } else { None }, &context.home()?, network, endpoint, true, network_retries, - &network_pins, + &mut network_pins, )?; let ProgramData::Bytecode(program_src) = program.data else { panic!("Expected bytecode when fetching a remote program"); diff --git a/crates/leo/src/cli/commands/common/util.rs b/crates/leo/src/cli/commands/common/util.rs index fc69a3ccc1d..b08a040e7e2 100644 --- a/crates/leo/src/cli/commands/common/util.rs +++ b/crates/leo/src/cli/commands/common/util.rs @@ -133,12 +133,13 @@ pub fn load_extra_programs_into_vm( println!("⬇️ Fetching remote program {name} and its dependencies from {endpoint}..."); let program_id = ProgramID::::from_str(&name) .map_err(|e| crate::errors::custom(format!("Failed to parse program ID '{name}': {e}")))?; - let fetched = super::query::load_pinned_programs_from_network( + let fetched = super::query::load_programs_from_network( context, program_id, network, endpoint, network_retries, + None, )?; extras.extend(fetched.into_iter().map(|(p, ed)| (p, ed.unwrap_or(LOCAL_PROGRAM_DEFAULT_EDITION)))); } @@ -156,7 +157,7 @@ mod tests { use std::str::FromStr; #[test] - fn extra_remote_program_requires_a_trusted_pin() { + fn extra_remote_program_reports_unreachable_endpoint() { use crate::cli::context::Context; use snarkvm::prelude::{ VM, @@ -179,8 +180,8 @@ mod tests { Some("http://localhost:1"), 0, ) - .expect_err("An unpinned extra program must fail"); - assert!(error.to_string().contains("trusted checksum pin"), "{error}"); + .expect_err("An unreachable extra program must fail"); + assert!(error.to_string().contains("failed to retrieve"), "{error}"); }); assert!(!root.join("cache").exists()); std::fs::remove_dir_all(root).expect("The temporary directory must be removed"); diff --git a/crates/leo/src/cli/commands/deploy.rs b/crates/leo/src/cli/commands/deploy.rs index 18e9483d974..87e75d0ca45 100644 --- a/crates/leo/src/cli/commands/deploy.rs +++ b/crates/leo/src/cli/commands/deploy.rs @@ -148,7 +148,7 @@ impl Command for LeoDeploy { let home_path = context.home()?; let network = get_network(&self.env_override.network)?; let endpoint = get_endpoint(&self.env_override.endpoint)?; - return Package::from_aleo_file_with_network_lock( + return Package::from_aleo_file( path, home_path, self.imports_dir.as_deref(), @@ -157,7 +157,6 @@ impl Command for LeoDeploy { Some(network), Some(&endpoint), self.env_override.network_retries, - context.network_lock.as_deref(), ); } if self.imports_dir.is_some() { diff --git a/crates/leo/src/cli/commands/execute.rs b/crates/leo/src/cli/commands/execute.rs index ebaf16ced88..63b7296fcbf 100644 --- a/crates/leo/src/cli/commands/execute.rs +++ b/crates/leo/src/cli/commands/execute.rs @@ -123,7 +123,7 @@ impl Command for LeoExecute { if context.package_filter.is_some() { return Err(crate::errors::custom("`--package` cannot be used with an Aleo bytecode file.").into()); } - return Package::from_aleo_file_with_network_lock( + return Package::from_aleo_file( path, home_path, self.imports_dir.as_deref(), @@ -132,7 +132,6 @@ impl Command for LeoExecute { Some(network), Some(&endpoint), self.env_override.network_retries, - context.network_lock.as_deref(), ) .map(Some); } @@ -418,12 +417,13 @@ fn handle_execute( // Note: The dependencies are downloaded in "post-order" (child before parent). if !is_local { println!("⬇️ Downloading {program_name} and its dependencies from {endpoint}..."); - programs = load_pinned_programs_from_network( + programs = load_programs_from_network( &context, program_id, network, &endpoint, command.env_override.network_retries, + None, )?; }; diff --git a/crates/leo/src/cli/commands/run.rs b/crates/leo/src/cli/commands/run.rs index 2699041e2a3..49f84d9aee6 100644 --- a/crates/leo/src/cli/commands/run.rs +++ b/crates/leo/src/cli/commands/run.rs @@ -104,7 +104,7 @@ impl Command for LeoRun { DEFAULT_ENDPOINT.to_string() } }; - return Package::from_aleo_file_with_network_lock( + return Package::from_aleo_file( path, home_path, self.imports_dir.as_deref(), @@ -113,7 +113,6 @@ impl Command for LeoRun { Some(network), Some(&endpoint), self.env_override.network_retries, - context.network_lock.as_deref(), ) .map(Some); } @@ -315,12 +314,13 @@ fn handle_run( let endpoint = get_endpoint(&command.env_override.endpoint)?; println!("⬇️ Downloading {program_name} and its dependencies from {endpoint}..."); // Load the programs from the network. - programs = load_pinned_programs_from_network( + programs = load_programs_from_network( &context, program_id, network, &endpoint, command.env_override.network_retries, + None, )?; }; diff --git a/crates/leo/src/cli/commands/synthesize.rs b/crates/leo/src/cli/commands/synthesize.rs index c16b4e8435d..34287d80784 100644 --- a/crates/leo/src/cli/commands/synthesize.rs +++ b/crates/leo/src/cli/commands/synthesize.rs @@ -173,12 +173,13 @@ fn handle_synthesize( // Note: The dependencies are downloaded in "post-order" (child before parent). if !is_local { println!("⬇️ Downloading {program_id} and its dependencies from {endpoint}..."); - programs = load_pinned_programs_from_network( + programs = load_programs_from_network( &context, program_id, network, &endpoint, command.env_override.network_retries, + None, )?; }; diff --git a/crates/leo/src/cli/commands/upgrade.rs b/crates/leo/src/cli/commands/upgrade.rs index 30960dedfbd..406479f529c 100644 --- a/crates/leo/src/cli/commands/upgrade.rs +++ b/crates/leo/src/cli/commands/upgrade.rs @@ -92,7 +92,7 @@ impl Command for LeoUpgrade { let home_path = context.home()?; let network = get_network(&self.env_override.network)?; let endpoint = get_endpoint(&self.env_override.endpoint)?; - return Package::from_aleo_file_with_network_lock( + return Package::from_aleo_file( path, home_path, self.imports_dir.as_deref(), @@ -101,7 +101,6 @@ impl Command for LeoUpgrade { Some(network), Some(&endpoint), self.env_override.network_retries, - context.network_lock.as_deref(), ); } if self.imports_dir.is_some() { @@ -265,13 +264,28 @@ fn handle_upgrade>( command.env_override.network_retries, )?; - // Load trusted old programs and their imports before checking or authorizing the upgrade. + // Resolve the current edition of each upgrade target before checking compatibility. let mut programs_and_editions = Vec::with_capacity(program_ids.len()); let mut loaded = HashSet::new(); for id in &program_ids { - for (program, edition) in - load_pinned_programs_from_network(&context, *id, network, &endpoint, command.env_override.network_retries)? - { + let edition = if local.iter().any(|task| task.id == *id) { + Some(leo_package::fetch_latest_edition( + &id.to_string(), + &endpoint, + network, + command.env_override.network_retries, + )?) + } else { + None + }; + for (program, edition) in load_programs_from_network( + &context, + *id, + network, + &endpoint, + command.env_override.network_retries, + edition, + )? { if loaded.insert(*program.id()) { programs_and_editions.push((program, edition.unwrap_or(LOCAL_PROGRAM_DEFAULT_EDITION))); } @@ -512,7 +526,7 @@ fn handle_upgrade>( } fn validate_upgrade_tasks<'a, N: Network>( - verified_programs: &'a [(Program, u16)], + network_programs: &'a [(Program, u16)], tasks: &[Task], skipped: &HashSet>, consensus_version: ConsensusVersion, @@ -525,10 +539,10 @@ fn validate_upgrade_tasks<'a, N: Network>( continue; } - let (remote_program, _) = verified_programs + let (remote_program, _) = network_programs .iter() .find(|(remote, _)| remote.id() == id) - .ok_or_else(|| crate::errors::custom(format!("Missing verified program for upgrade: {id}")))?; + .ok_or_else(|| crate::errors::custom(format!("Missing deployed program for upgrade: {id}")))?; reject_invalid_upgrade(id, remote_program, program, consensus_version)?; remote_programs.push(remote_program); } diff --git a/crates/leo/src/cli/helpers/context.rs b/crates/leo/src/cli/helpers/context.rs index 3f86a8d7943..d401ca629c0 100644 --- a/crates/leo/src/cli/helpers/context.rs +++ b/crates/leo/src/cli/helpers/context.rs @@ -30,8 +30,6 @@ pub struct Context { pub path: Option, /// Path to use for the Aleo registry, None when default pub home: Option, - /// Optional file with trusted network program pins. - pub network_lock: Option, /// Recursive flag. // TODO: Shift from callee to caller by including display method pub recursive: bool, @@ -46,7 +44,7 @@ impl Context { recursive: bool, package_filter: Option, ) -> Result { - Ok(Context { path, home, network_lock: None, recursive, package_filter }) + Ok(Context { path, home, recursive, package_filter }) } /// Returns the path of the parent directory to the Leo package. @@ -77,18 +75,20 @@ impl Context { } } - /// Read trusted pins from the explicit file or the package lock. + /// Read dependency pins from the enclosing project or workspace. pub fn network_pins(&self) -> Result { - if let Some(path) = &self.network_lock { - return Lock::read_file(path); - } - let path = self.dir()?; + let mut path = self.dir()?; if path.extension().and_then(|extension| extension.to_str()) == Some("aleo") { - let path = path.canonicalize().map_err(crate::errors::cli_io_error)?; - return Lock::read(path.parent().unwrap_or_else(|| std::path::Path::new("."))); + path = path.canonicalize().map_err(crate::errors::cli_io_error)?; + path.pop(); + } + if let Some(root) = Workspace::discover_root(&path)? { + Lock::read(&root) + } else if let Some(root) = path.ancestors().find(|dir| dir.join(leo_package::MANIFEST_FILENAME).is_file()) { + Lock::read(root) + } else { + Ok(Lock::default()) } - let root = Workspace::discover_root(&path)?.unwrap_or(path); - Lock::read(&root) } /// Opens the manifest file `program.json`. @@ -147,12 +147,6 @@ impl Context { /// Create a new `Context` pointing at a specific directory. pub fn with_path(&self, path: PathBuf) -> Self { - Context { - path: Some(path), - home: self.home.clone(), - network_lock: self.network_lock.clone(), - recursive: self.recursive, - package_filter: None, - } + Context { path: Some(path), home: self.home.clone(), recursive: self.recursive, package_filter: None } } } diff --git a/crates/package/README.md b/crates/package/README.md index 5be95dc56e6..535f6ff1a95 100644 --- a/crates/package/README.md +++ b/crates/package/README.md @@ -4,39 +4,29 @@ [![Authors](https://img.shields.io/badge/authors-Aleo-orange.svg)](../AUTHORS) [![License](https://img.shields.io/badge/License-GPLv3-blue.svg)](./LICENSE.md) -## Trusted network programs +## Network dependency locking -Network programs require trusted checksum pins in `leo.lock`. The `program.json` format does not change. A pin specifies a program ID, network, edition, and checksum. Leo checks the pin before it accepts cached or downloaded bytecode. +Leo creates network entries in `leo.lock` automatically when it resolves project dependencies. Each entry records the program ID, network, edition, and checksum. This includes transitive network imports. The `program.json` format does not change, and users do not need to enter checksums. -The lock format is JSON. Version 2 adds a `network` array to the existing `git` array. Each network entry has these fields: +The first download trusts the configured endpoint. Leo checks the program ID and size, then records the checksum of the canonical Aleo program. An old cache entry cannot supply the first checksum. This protects subsequent builds against changed bytecode, but it does not authenticate the first response from a malicious endpoint. -| Field | Value | -| --- | --- | -| `name` | The full program ID, for example `token.aleo`. | -| `network` | `mainnet`, `testnet`, or `canary`. | -| `edition` | The trusted edition number, from 0 to 65535. | -| `checksum` | An array of 32 integers, each from 0 to 255. | - -Use one entry for each program and network. Include the remote target and every transitive import. The checksum is the SHA3-256 hash of the canonical program text from snarkVM `Program::to_checksum()`. A hash of the raw file can differ because of comments or spacing. - -To obtain a checksum from reviewed Leo source, run this command in that source package: +Later builds use the locked edition. Leo checks cached and downloaded bytecode against its checksum. A checksum mismatch stops the operation without replacing the lock or cache. `--no-cache` downloads the locked edition again; it does not select a new edition or replace its checksum. -```sh -leo build --checksums --json-output=checksums.json -``` +To update a dependency, use `leo add` with the required `--edition`. A different edition is fetched from the endpoint and gets a new checksum. Re-adding a dependency without an edition selects the latest edition. If the edition is unchanged, the existing checksum still applies. -Copy the `program_checksum` byte array from the JSON output into the pin's `checksum` field. Obtain the program ID, network, and edition from an independent trusted source. For existing Aleo bytecode, use the snarkVM `Program::to_checksum()` method after you review the code. Do not create a trusted pin from an unchecked endpoint response or an old cache entry. +Leo reads and writes the lock at the workspace root, or beside `program.json` for a standalone project. It preserves network entries for other workspace members, development dependencies, and networks. A build writes the lock only after all dependencies are resolved and the dependency graph is valid. The file replacement is atomic. -Leo reads `leo.lock` from the workspace root, or from the package directory when there is no workspace. For a standalone `.aleo` file, Leo reads the lock beside that file. To use a separate trust file, pass its path: +Standalone `.aleo` files can resolve network imports without manual pins. They do not create a project lock. Remote command targets can also load without a project; their first response has the same endpoint trust limit. Upgrade checks must inspect the current deployed edition. -```sh -leo execute token.aleo/transfer --network-lock /path/to/trusted.lock -``` +The lock format is JSON. Version 2 adds a `network` array to the existing `git` array. A valid version 1 Git lock remains readable. Each network entry has these fields: -The global `--network-lock` option also applies to builds, remote `--with` programs, and upgrades. Leo does not write to a separate trust file. Git resolution still updates the package or workspace lock. Use a trust file that you control; a dependency's own lock does not supply trusted pins. - -An omitted manifest edition uses the pinned edition. An explicit edition must match the pin. Leo does not select the highest cached edition, request the latest edition, or fall back to an unversioned program URL. To approve an upgrade, replace the pin with the independently verified edition and checksum. For `leo upgrade`, pin the old deployed program and its imports so Leo can check the upgrade against trusted code. +| Field | Value | +| --- | --- | +| `name` | The full program ID, for example `token.aleo`. | +| `network` | `mainnet`, `testnet`, or `canary`. | +| `edition` | The resolved edition number, from 0 to 65535. | +| `checksum` | An array of 32 integers, each from 0 to 255. | -Missing pins, checksum mismatches, duplicate pins, and invalid lock files stop the operation. Neither `--yes` nor `--no-cache` bypasses verification. A valid version 1 Git lock remains readable, but network dependencies require pins. Builds preserve network pins, including pins not used by the current package. The built-in `credits.aleo` program comes from snarkVM at edition zero and needs no external pin. +The checksum is the SHA3-256 hash from snarkVM `Program::to_checksum()`. Comments and spacing do not change it. Duplicate entries and invalid lock files stop resolution. The built-in `credits.aleo` program comes from snarkVM at edition zero and needs no network entry. -Pins authenticate program content. They do not authenticate other node responses, such as balances or block heights. +Checksums protect the recorded program content. They do not authenticate other node responses, such as balances or block heights. Keep `leo.lock` under version control with the project. diff --git a/crates/package/src/compilation_unit.rs b/crates/package/src/compilation_unit.rs index cfb163ff19a..f649ef0f3cb 100644 --- a/crates/package/src/compilation_unit.rs +++ b/crates/package/src/compilation_unit.rs @@ -205,7 +205,7 @@ impl CompilationUnit { }) } - /// Fetch a program at its trusted pinned edition and verify its bytecode. + /// Fetch a locked edition, or record the first valid response from the configured endpoint. #[allow(clippy::too_many_arguments)] pub fn fetch>( name: Symbol, @@ -215,7 +215,7 @@ impl CompilationUnit { endpoint: &str, no_cache: bool, network_retries: u32, - pins: &Lock, + pins: &mut Lock, ) -> Result { Self::fetch_impl(name, edition, home_path.as_ref(), network, endpoint, no_cache, network_retries, pins) } @@ -229,7 +229,7 @@ impl CompilationUnit { endpoint: &str, no_cache: bool, network_retries: u32, - pins: &Lock, + pins: &mut Lock, ) -> Result { // Validate the name before using it in cache paths or network URLs. let program_id = canonicalize_program_name(&name.to_string()) @@ -260,11 +260,14 @@ impl CompilationUnit { }); } - let pin = pins.network_pin(&full_name, network, edition)?; - let edition = pin.edition; + let pin = pins.network_pin(&full_name, network, edition); + let edition = match pin.map(|pin| pin.edition).or(edition) { + Some(edition) => edition, + None => crate::fetch_latest_edition(&full_name, endpoint, network, network_retries)?, + }; let cache_directory = home_path.join(format!("registry/{network}/{name}/{edition}")); let full_cache_path = cache_directory.join(&full_name); - let use_cache = !no_cache && full_cache_path.exists(); + let use_cache = pin.is_some() && !no_cache && full_cache_path.exists(); let bytecode = if use_cache { std::fs::read_to_string(&full_cache_path).map_err(|err| { crate::errors::util_file_io_error( @@ -277,7 +280,31 @@ impl CompilationUnit { let url = format!("{endpoint}/{network}/program/{full_name}/{edition}"); fetch_from_network(&url, network_retries)? }; - pin.verify(&bytecode)?; + let new_pin = if let Some(pin) = pin { + pin.verify(&bytecode)?; + None + } else { + if bytecode.len() > MAX_PROGRAM_SIZE { + return Err( + crate::errors::program_size_limit_exceeded(&full_name, bytecode.len(), MAX_PROGRAM_SIZE).into() + ); + } + let program: SvmProgram = + bytecode.parse().map_err(|_| crate::errors::snarkvm_parsing_error(name))?; + if program.id().to_string() != full_name { + return Err(crate::errors::untrusted_network_program( + &full_name, + "the bytecode declares a different program ID", + ) + .into()); + } + Some(NetworkLockEntry { + name: full_name.clone(), + network: network.to_string(), + edition, + checksum: program.to_checksum().map(|byte| *byte), + }) + }; let dependencies = parse_dependencies_from_aleo(name, &bytecode, &IndexMap::new())?; if !use_cache { @@ -295,6 +322,10 @@ impl CompilationUnit { })?; } + if let Some(pin) = new_pin { + pins.record_network(pin); + } + Ok(Self { name: Symbol::intern(&full_name), data: ProgramData::Bytecode(bytecode), diff --git a/crates/package/src/errors.rs b/crates/package/src/errors.rs index 71bbf5e0ed5..52297ab5d95 100644 --- a/crates/package/src/errors.rs +++ b/crates/package/src/errors.rs @@ -26,12 +26,12 @@ const CODE_MASK: i32 = 5000; pub(crate) fn invalid_lock_file(path: impl Display, reason: impl Display) -> Backtraced { Backtraced::error(CODE_PREFIX, CODE_MASK + 84, format!("invalid lock file at `{path}`: {reason}")) - .with_help("Restore a valid lock file. Do not replace trusted pins with values from an untrusted endpoint.") + .with_help("Restore a valid lock file from version control.") } pub(crate) fn untrusted_network_program(name: impl Display, reason: impl Display) -> Backtraced { Backtraced::error(CODE_PREFIX, CODE_MASK + 85, format!("cannot trust network program `{name}`: {reason}")) - .with_help("Add the program's network, edition, and checksum from reviewed bytecode or an independent trusted source to `leo.lock`. Use `--network-lock ` to select a trusted lock file.") + .with_help("Check the configured endpoint and cached program. To change a dependency edition, run `leo add` with `--edition`; do not replace its checksum to bypass verification.") } pub(crate) fn io_error_gitignore_file(error: impl ErrorArg) -> Backtraced { diff --git a/crates/package/src/git.rs b/crates/package/src/git.rs index ad2a9940898..3c4a5cef7af 100644 --- a/crates/package/src/git.rs +++ b/crates/package/src/git.rs @@ -183,7 +183,7 @@ fn checkout_tree( } /// A unique directory under `parent` for transient work (`--`). -fn unique_dir(parent: &Path, prefix: &str) -> PathBuf { +pub(crate) fn unique_dir(parent: &Path, prefix: &str) -> PathBuf { static COUNTER: AtomicU64 = AtomicU64::new(0); let seq = COUNTER.fetch_add(1, Ordering::Relaxed); parent.join(format!("{prefix}-{}-{seq}", std::process::id())) diff --git a/crates/package/src/lock.rs b/crates/package/src/lock.rs index 16de43c33ee..68114b00059 100644 --- a/crates/package/src/lock.rs +++ b/crates/package/src/lock.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -//! The `leo.lock` file pins git commits and trusted network program checksums. +//! The `leo.lock` file pins Git commits and network program checksums. use leo_ast::NetworkName; use leo_errors::Result; @@ -22,7 +22,7 @@ use snarkvm::prelude::{Program, ProgramID, TestnetV0}; use indexmap::IndexSet; use serde::{Deserialize, Serialize}; -use std::path::Path; +use std::{io::Write, path::Path}; /// File name of the lock file, stored alongside `program.json`. pub const LOCK_FILENAME: &str = "leo.lock"; @@ -39,7 +39,7 @@ pub struct GitLockEntry { pub commit: String, } -/// A program identity obtained from reviewed bytecode or an independent trusted source. +/// A program identity and checksum recorded when its edition was first fetched. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct NetworkLockEntry { @@ -73,7 +73,7 @@ impl NetworkLockEntry { if program.to_checksum().map(|byte| *byte) != self.checksum { return Err(crate::errors::untrusted_network_program( &self.name, - "the bytecode does not match the trusted checksum", + "the bytecode does not match the locked checksum", ) .into()); } @@ -110,13 +110,6 @@ impl Lock { Self::parse(&path, &contents) } - /// Read an explicitly selected trust file. Missing files are errors. - pub fn read_file(path: &Path) -> Result { - let contents = - std::fs::read_to_string(path).map_err(|err| crate::errors::invalid_lock_file(path.display(), err))?; - Self::parse(path, &contents) - } - fn parse(path: &Path, contents: &str) -> Result { let mut lock: Self = serde_json::from_str(contents).map_err(|err| crate::errors::invalid_lock_file(path.display(), err))?; @@ -139,21 +132,19 @@ impl Lock { Ok(lock) } - /// Select the trusted edition. Network responses and cache contents cannot select a pin. - pub fn network_pin(&self, name: &str, network: NetworkName, edition: Option) -> Result<&NetworkLockEntry> { + /// Find a network pin that matches the requested program and optional edition. + pub fn network_pin(&self, name: &str, network: NetworkName, edition: Option) -> Option<&NetworkLockEntry> { let name = crate::canonicalize_program_name(name); let network = network.to_string(); - let pin = self.network.iter().find(|pin| pin.name == name && pin.network == network).ok_or_else(|| { - crate::errors::untrusted_network_program(&name, "no trusted checksum pin for this network") - })?; - if edition.is_some_and(|edition| edition != pin.edition) { - return Err(crate::errors::untrusted_network_program( - &name, - "the requested edition does not match the trusted pin", - ) - .into()); - } - Ok(pin) + self.network.iter().find(|pin| { + pin.name == name && pin.network == network && edition.is_none_or(|edition| edition == pin.edition) + }) + } + + /// Record a network edition, preserving entries for other programs and networks. + pub fn record_network(&mut self, entry: NetworkLockEntry) { + self.network.retain(|pin| pin.name != entry.name || pin.network != entry.network); + self.network.push(entry); } /// The pinned commit for `(name, git, reference)`, or `None` (forcing re-resolution) on any mismatch. @@ -177,8 +168,11 @@ impl Lock { /// Carry over entries from `old` that were not re-recorded in this lock and that `keep` accepts. pub fn carry_over(&mut self, old: &Lock, mut keep: impl FnMut(&GitLockEntry) -> bool) { - // Network pins are user-managed, including pins not used by this build. - self.network.clone_from(&old.network); + for entry in &old.network { + if !self.network.iter().any(|pin| pin.name == entry.name && pin.network == entry.network) { + self.network.push(entry.clone()); + } + } for entry in &old.git { if self.commit_for(&entry.name, &entry.git, &entry.reference).is_none() && keep(entry) { self.git.push(entry.clone()); @@ -194,8 +188,20 @@ impl Lock { /// Write the lock to `dir`, with entries sorted for deterministic output. pub fn write(&mut self, dir: &Path) -> Result<()> { let path = dir.join(LOCK_FILENAME); + let permissions = match std::fs::symlink_metadata(&path) { + Ok(metadata) if metadata.is_file() => Some(metadata.permissions()), + Ok(_) => { + return Err(crate::errors::failed_to_write_lock( + path.display(), + "expected a regular file, not a symlink", + ) + .into()); + } + Err(err) if err.kind() == std::io::ErrorKind::NotFound => None, + Err(err) => return Err(crate::errors::failed_to_write_lock(path.display(), err).into()), + }; if self.is_empty() { - if path.exists() { + if permissions.is_some() { std::fs::remove_file(&path).map_err(|err| crate::errors::failed_to_write_lock(path.display(), err))?; } return Ok(()); @@ -206,7 +212,25 @@ impl Lock { let mut contents = serde_json::to_string_pretty(self) .map_err(|err| crate::errors::failed_to_serialize_lock(path.display(), err))?; contents.push('\n'); - std::fs::write(&path, contents).map_err(|err| crate::errors::failed_to_write_lock(path.display(), err))?; + // Replace the complete lock only after its temporary file is written. + let temporary = crate::git::unique_dir(dir, ".leo.lock"); + let mut file = std::fs::OpenOptions::new() + .write(true) + .create_new(true) + .open(&temporary) + .map_err(|err| crate::errors::failed_to_write_lock(path.display(), err))?; + let result = (|| { + if let Some(permissions) = permissions { + file.set_permissions(permissions)?; + } + file.write_all(contents.as_bytes())?; + drop(file); + std::fs::rename(&temporary, &path) + })(); + if let Err(err) = result { + let _ = std::fs::remove_file(&temporary); + return Err(crate::errors::failed_to_write_lock(path.display(), err).into()); + } Ok(()) } diff --git a/crates/package/src/package.rs b/crates/package/src/package.rs index d1b2ca6b7b3..cb9cdd2aff3 100644 --- a/crates/package/src/package.rs +++ b/crates/package/src/package.rs @@ -254,7 +254,6 @@ impl Package { network, endpoint, network_retries, - None, ) } @@ -283,33 +282,6 @@ impl Package { network, endpoint, network_retries, - None, - ) - } - - /// Load the package with an optional trusted network lock file. - #[allow(clippy::too_many_arguments)] - pub fn from_aleo_file_with_network_lock, Q: AsRef>( - path: P, - home_path: Q, - imports_directory: Option<&Path>, - no_cache: bool, - no_local: bool, - network: Option, - endpoint: Option<&str>, - network_retries: u32, - network_lock: Option<&Path>, - ) -> Result { - Self::from_aleo_file_impl( - path.as_ref(), - home_path.as_ref(), - imports_directory, - no_cache, - no_local, - network, - endpoint, - network_retries, - network_lock, ) } @@ -337,35 +309,6 @@ impl Package { network, endpoint, network_retries, - None, - ) - } - - /// Load the package with an optional trusted network lock file. - #[allow(clippy::too_many_arguments)] - pub fn from_directory_with_network_lock, Q: AsRef>( - path: P, - home_path: Q, - no_cache: bool, - no_local: bool, - offline: bool, - network: Option, - endpoint: Option<&str>, - network_retries: u32, - network_lock: Option<&Path>, - ) -> Result { - Self::from_directory_impl( - path.as_ref(), - home_path.as_ref(), - /* build_graph */ true, - /* with_tests */ false, - no_cache, - no_local, - offline, - network, - endpoint, - network_retries, - network_lock, ) } @@ -393,35 +336,6 @@ impl Package { network, endpoint, network_retries, - None, - ) - } - - /// Load the package with an optional trusted network lock file. - #[allow(clippy::too_many_arguments)] - pub fn from_directory_with_tests_and_network_lock, Q: AsRef>( - path: P, - home_path: Q, - no_cache: bool, - no_local: bool, - offline: bool, - network: Option, - endpoint: Option<&str>, - network_retries: u32, - network_lock: Option<&Path>, - ) -> Result { - Self::from_directory_impl( - path.as_ref(), - home_path.as_ref(), - /* build_graph */ true, - /* with_tests */ true, - no_cache, - no_local, - offline, - network, - endpoint, - network_retries, - network_lock, ) } @@ -456,7 +370,6 @@ impl Package { network: Option, endpoint: Option<&str>, network_retries: u32, - network_lock: Option<&Path>, ) -> Result { if path.extension().and_then(|extension| extension.to_str()) != Some("aleo") { return Err(anyhow!("Expected an Aleo bytecode file with the `.aleo` extension: {}", path.display()).into()); @@ -510,11 +423,13 @@ impl Package { let mut map: IndexMap = IndexMap::new(); let mut digraph = DiGraph::new(Default::default()); let old_lock = Lock::default(); - let network_pins = match network_lock { - Some(path) => Lock::read_file(path)?, - None => Lock::read(&base_directory)?, - }; - let mut new_lock = Lock::default(); + let lock_directory = Workspace::discover_root(&base_directory)?.or_else(|| { + base_directory + .ancestors() + .find(|directory| directory.join(MANIFEST_FILENAME).is_file()) + .map(Path::to_path_buf) + }); + let mut new_lock = lock_directory.as_deref().map(Lock::read).transpose()?.unwrap_or_default(); Self::graph_build( &home_path, network, @@ -529,7 +444,6 @@ impl Package { network_retries, &declared_deps, &old_lock, - &network_pins, &mut new_lock, false, )?; @@ -570,7 +484,6 @@ impl Package { network: Option, endpoint: Option<&str>, network_retries: u32, - network_lock: Option<&Path>, ) -> Result { let map_err = |path: &Path, err| { crate::errors::util_file_io_error(format_args!("Trying to find path at {}", path.display()), err) @@ -598,11 +511,10 @@ impl Package { // The lock lives at the workspace root, else beside this package's `program.json`. let lock_dir = workspace_root.as_deref().unwrap_or(&path).to_path_buf(); - // New lock records only this build's resolutions; others are carried over from the old lock after. + // Reuse network pins and record Git resolutions for this build. let old_lock = Lock::read(&lock_dir)?; - let override_lock = network_lock.map(Lock::read_file).transpose()?; - let network_pins = override_lock.as_ref().unwrap_or(&old_lock); let mut new_lock = Lock::default(); + new_lock.carry_over(&old_lock, |_| false); let first_dependency = Dependency { name: manifest.program.clone(), @@ -657,7 +569,6 @@ impl Package { network_retries, &declared_deps, &old_lock, - network_pins, &mut new_lock, offline, )?; @@ -681,12 +592,12 @@ impl Package { }; new_lock.carry_over(&old_lock, |entry| dev_git_names.contains(&entry.name.as_str())); } - // Persist the lock (and drop a stale one when no git deps remain). - new_lock.write(&lock_dir)?; - let ordered_dependency_symbols = digraph.post_order().map_err(|_| crate::errors::circular_dependency_error())?; + // Commit the complete lock only after the dependency graph is valid. + new_lock.write(&lock_dir)?; + ( ordered_dependency_symbols.into_iter().map(|symbol| map.swap_remove(&symbol).unwrap().1).collect(), digraph, @@ -713,7 +624,6 @@ impl Package { network_retries: u32, declared_deps: &IndexMap, old_lock: &Lock, - network_pins: &Lock, new_lock: &mut Lock, offline: bool, ) -> Result<()> { @@ -797,7 +707,7 @@ impl Package { endpoint, no_cache, network_retries, - network_pins, + new_lock, )? } (_, Location::Git) => CompilationUnit::from_git( @@ -872,7 +782,6 @@ impl Package { network_retries, declared_deps, old_lock, - network_pins, new_lock, offline, )?; @@ -1218,6 +1127,7 @@ function main: let imports = root.join("imports"); let home = root.join("home"); crate::test_util::write_file(&program_path, MAIN_PROGRAM); + crate::test_util::write_file(&root.join(MANIFEST_FILENAME), r#"{"program":"standalone.aleo"}"#); crate::test_util::write_file(&imports.join("leaf.aleo"), LEAF_PROGRAM); crate::test_util::write_file( &home.join("registry/testnet/dependency/0/dependency.aleo"), diff --git a/crates/package/src/tests.rs b/crates/package/src/tests.rs index bec795b9db5..24035b5e444 100644 --- a/crates/package/src/tests.rs +++ b/crates/package/src/tests.rs @@ -70,14 +70,16 @@ fn write_network_lock(directory: &Path, pins: &[serde_json::Value]) -> Lock { Lock::read(directory).expect("fixture lock must be valid") } -fn network_response(status: &str, body: &str) -> (String, JoinHandle>) { +fn network_response(responses: &[(&str, &str, &str)]) -> (String, JoinHandle>) { let listener = TcpListener::bind("127.0.0.1:0").expect("fixture listener must bind"); listener.set_nonblocking(true).expect("fixture listener must be nonblocking"); let endpoint = format!("http://{}", listener.local_addr().expect("fixture address must exist")); - let response = format!( - "HTTP/1.1 {status}\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}", - body.len() - ); + let responses: Vec<_> = responses.iter().map(|(path, status, body)| { + (format!("GET {path} HTTP/1.1\r\n"), format!( + "HTTP/1.1 {status}\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}", + body.len() + )) + }).collect(); let handle = thread::spawn(move || { let mut requests = Vec::new(); let mut deadline = Instant::now() + Duration::from_secs(2); @@ -94,7 +96,12 @@ fn network_response(status: &str, body: &str) -> (String, JoinHandle assert!(count > 0 && request.len() < 8192, "fixture request must contain bounded headers"); request.extend_from_slice(&buffer[..count]); } - requests.push(String::from_utf8(request).expect("fixture request must be UTF-8")); + let request = String::from_utf8(request).expect("fixture request must be UTF-8"); + let (_, response) = responses + .iter() + .find(|(prefix, _)| prefix == "GET HTTP/1.1\r\n" || request.starts_with(prefix)) + .unwrap_or_else(|| panic!("unexpected request: {request}")); + requests.push(request); stream.write_all(response.as_bytes()).expect("fixture response must be sent"); deadline = Instant::now() + Duration::from_millis(200); assert!(requests.len() < 10, "fetch must not make unbounded requests"); @@ -111,25 +118,32 @@ fn network_response(status: &str, body: &str) -> (String, JoinHandle } #[test] -fn cached_network_program_requires_trusted_pin() { +fn first_network_fetch_ignores_unverified_cache_and_records_checksum() { leo_span::create_session_if_not_set_then(|_| { - let home = unique_dir("network-pin-required"); - write_file( - &home.join("registry/testnet/token/0/token.aleo"), - "program token.aleo;\nfunction reveal:\n input r0 as u32.public;\n output r0 as u32.public;\n", - ); - let result = crate::CompilationUnit::fetch( + let home = unique_dir("network-first-fetch"); + let cache = home.join("registry/testnet/token/0/token.aleo"); + write_file(&cache, &TRUSTED_TOKEN.replace("private", "public")); + let (endpoint, server) = network_response(&[("", "200 OK", TRUSTED_TOKEN)]); + let mut lock = Lock::default(); + let unit = crate::CompilationUnit::fetch( Symbol::intern("token.aleo"), Some(0), &home, leo_ast::NetworkName::TestnetV0, - "http://127.0.0.1:1", + &endpoint, false, 0, - &Lock::default(), - ); + &mut lock, + ) + .expect("the first download must create its checksum automatically"); + assert_eq!(unit.edition, Some(0)); + assert_eq!(server.join().expect("fixture must finish").len(), 1); + assert_eq!(std::fs::read_to_string(cache).expect("cache must exist"), TRUSTED_TOKEN); + lock.network_pin("token.aleo", leo_ast::NetworkName::TestnetV0, Some(0)) + .expect("the first download must record a checksum") + .verify(TRUSTED_TOKEN) + .expect("the downloaded bytecode must match the new checksum"); std::fs::remove_dir_all(home).expect("test directory must be removed"); - assert!(result.is_err(), "cached bytecode must not be trusted without an independent pin"); }); } @@ -137,7 +151,7 @@ fn cached_network_program_requires_trusted_pin() { fn cached_network_program_checks_canonical_bytes_on_every_read() { leo_span::create_session_if_not_set_then(|_| { let home = unique_dir("network-cache-verification"); - let lock = write_network_lock(&home, &[network_pin(TRUSTED_TOKEN, 0)]); + let mut lock = write_network_lock(&home, &[network_pin(TRUSTED_TOKEN, 0)]); let cache = home.join("registry/testnet/token/0/token.aleo"); let formatted = format!("// Independent formatting does not change canonical bytes.\n{TRUSTED_TOKEN}\n"); write_file(&cache, &formatted); @@ -149,7 +163,7 @@ fn cached_network_program_checks_canonical_bytes_on_every_read() { "http://127.0.0.1:1", false, 0, - &lock, + &mut lock, ) .expect("approved canonical bytecode must load from cache"); assert_eq!(unit.edition, Some(0)); @@ -168,7 +182,7 @@ fn cached_network_program_checks_canonical_bytes_on_every_read() { "http://127.0.0.1:1", false, 0, - &lock, + &mut lock, ) .expect_err("changed same-ID bytecode must fail on the next read"); assert!(error.to_string().contains("checksum"), "{error}"); @@ -182,7 +196,7 @@ fn cached_network_program_checks_canonical_bytes_on_every_read() { fn inferred_network_edition_comes_from_pin_not_cache() { leo_span::create_session_if_not_set_then(|_| { let home = unique_dir("network-pinned-edition"); - let lock = write_network_lock(&home, &[network_pin(TRUSTED_TOKEN, 2)]); + let mut lock = write_network_lock(&home, &[network_pin(TRUSTED_TOKEN, 2)]); write_file(&home.join("registry/testnet/token/2/token.aleo"), TRUSTED_TOKEN); write_file(&home.join("registry/testnet/token/99/token.aleo"), &TRUSTED_TOKEN.replace("private", "public")); let unit = crate::CompilationUnit::fetch( @@ -193,7 +207,7 @@ fn inferred_network_edition_comes_from_pin_not_cache() { "http://127.0.0.1:1", false, 0, - &lock, + &mut lock, ) .expect("the trusted edition must load without a latest-edition request"); assert_eq!(unit.edition, Some(2)); @@ -203,34 +217,41 @@ fn inferred_network_edition_comes_from_pin_not_cache() { } #[test] -fn network_pin_mismatch_fails_before_io() { +fn explicit_edition_change_records_fresh_checksum_and_preserves_other_networks() { leo_span::create_session_if_not_set_then(|_| { - let home = unique_dir("network-pin-mismatch"); - let lock = write_network_lock(&home, &[network_pin(TRUSTED_TOKEN, 2)]); - let (endpoint, server) = network_response("200 OK", TRUSTED_TOKEN); - for (name, network, edition) in [ - ("token.aleo", leo_ast::NetworkName::MainnetV0, Some(2)), - ("token.aleo", leo_ast::NetworkName::TestnetV0, Some(3)), - ("missing.aleo", leo_ast::NetworkName::TestnetV0, None), - ] { - let error = crate::CompilationUnit::fetch( - Symbol::intern(name), - edition, - &home, - network, - &endpoint, - false, - 0, - &lock, - ) - .expect_err("missing or mismatched pins must fail before fetching"); - assert!(error.to_string().contains("pin"), "{error}"); - } - assert!( - server.join().expect("fixture must finish").is_empty(), - "untrusted requests must not reach the endpoint" - ); - assert!(!home.join("registry").exists()); + let home = unique_dir("network-edition-change"); + let mut mainnet = network_pin(TRUSTED_TOKEN, 2); + mainnet["network"] = serde_json::json!("mainnet"); + let mut lock = write_network_lock(&home, &[network_pin(TRUSTED_TOKEN, 2), mainnet]); + let old = lock.clone(); + let updated = TRUSTED_TOKEN.replace("private", "public"); + write_file(&home.join("registry/testnet/token/3/token.aleo"), TRUSTED_TOKEN); + let (endpoint, server) = network_response(&[("/testnet/program/token.aleo/3", "200 OK", &updated)]); + let unit = crate::CompilationUnit::fetch( + Symbol::intern("token.aleo"), + Some(3), + &home, + leo_ast::NetworkName::TestnetV0, + &endpoint, + false, + 0, + &mut lock, + ) + .expect("an explicit edition change must fetch and record the new edition"); + assert_eq!(unit.edition, Some(3)); + assert_eq!(server.join().expect("fixture must finish").len(), 1); + lock.carry_over(&old, |_| true); + lock.write(&home).expect("updated lock must write"); + let lock = Lock::read(&home).expect("updated lock must read"); + lock.network_pin("token.aleo", leo_ast::NetworkName::TestnetV0, Some(3)) + .expect("new edition must survive carry-over") + .verify(&updated) + .expect("new checksum must match"); + assert!(lock.network_pin("token.aleo", leo_ast::NetworkName::TestnetV0, Some(2)).is_none()); + lock.network_pin("token.aleo", leo_ast::NetworkName::MainnetV0, Some(2)) + .expect("other network must remain") + .verify(TRUSTED_TOKEN) + .expect("other checksum must remain"); std::fs::remove_dir_all(home).expect("test directory must be removed"); }); } @@ -239,9 +260,9 @@ fn network_pin_mismatch_fails_before_io() { fn network_fetch_uses_exact_pinned_edition() { leo_span::create_session_if_not_set_then(|_| { let home = unique_dir("network-exact-edition"); - let lock = write_network_lock(&home, &[network_pin(TRUSTED_TOKEN, 7)]); + let mut lock = write_network_lock(&home, &[network_pin(TRUSTED_TOKEN, 7)]); let body = serde_json::to_string(TRUSTED_TOKEN).expect("fixture body must serialize"); - let (endpoint, server) = network_response("200 OK", &body); + let (endpoint, server) = network_response(&[("", "200 OK", &body)]); let unit = crate::CompilationUnit::fetch( Symbol::intern("token.aleo"), None, @@ -250,7 +271,7 @@ fn network_fetch_uses_exact_pinned_edition() { &endpoint, false, 0, - &lock, + &mut lock, ) .expect("approved response must load"); let requests = server.join().expect("fixture must finish"); @@ -288,10 +309,10 @@ fn rejected_network_responses_do_not_replace_cache_or_fall_back() { let home = unique_dir("network-rejected-response"); let mut pin = network_pin(&checksum_source, 7); pin["name"] = serde_json::json!("token.aleo"); - let lock = write_network_lock(&home, &[pin]); + let mut lock = write_network_lock(&home, &[pin]); let cache = home.join("registry/testnet/token/7/token.aleo"); write_file(&cache, TRUSTED_TOKEN); - let (endpoint, server) = network_response(status, &bytecode); + let (endpoint, server) = network_response(&[("", status, &bytecode)]); let error = crate::CompilationUnit::fetch( Symbol::intern("token.aleo"), Some(7), @@ -300,7 +321,7 @@ fn rejected_network_responses_do_not_replace_cache_or_fall_back() { &endpoint, true, 0, - &lock, + &mut lock, ) .expect_err("an unapproved response must fail"); assert!(error.to_string().contains(reason), "{error}"); @@ -317,8 +338,8 @@ fn rejected_network_responses_do_not_replace_cache_or_fall_back() { fn rejected_network_response_does_not_create_a_cache() { leo_span::create_session_if_not_set_then(|_| { let home = unique_dir("network-rejected-new-cache"); - let lock = write_network_lock(&home, &[network_pin(TRUSTED_TOKEN, 0)]); - let (endpoint, server) = network_response("200 OK", &TRUSTED_TOKEN.replace("private", "public")); + let mut lock = write_network_lock(&home, &[network_pin(TRUSTED_TOKEN, 0)]); + let (endpoint, server) = network_response(&[("", "200 OK", &TRUSTED_TOKEN.replace("private", "public"))]); let error = crate::CompilationUnit::fetch( Symbol::intern("token.aleo"), Some(0), @@ -327,7 +348,7 @@ fn rejected_network_response_does_not_create_a_cache() { &endpoint, false, 0, - &lock, + &mut lock, ) .expect_err("an unapproved response must not enter a new cache"); assert!(error.to_string().contains("checksum"), "{error}"); @@ -368,7 +389,7 @@ fn bundled_credits_ignores_hostile_cache() { "http://127.0.0.1:1", no_cache, 0, - &Lock::default(), + &mut Lock::default(), ) .expect("bundled credits must work without a pin or endpoint"); assert!(matches!(unit.data, crate::ProgramData::Bytecode(ref bytecode) if bytecode == &expected)); @@ -383,7 +404,7 @@ fn bundled_credits_ignores_hostile_cache() { "http://127.0.0.1:1", no_cache, 0, - &Lock::default(), + &mut Lock::default(), ) .expect_err("Bundled credits must reject nonzero editions before contacting the endpoint"); assert!(error.to_string().contains("credits must use edition zero"), "{error}"); @@ -412,12 +433,10 @@ fn network_lock_rejects_malformed_and_ambiguous_trust() { ] { write_file(&directory.join(LOCK_FILENAME), &contents); assert!(Lock::read(&directory).is_err(), "invalid trust file accepted: {contents}"); - assert!(Lock::read_file(&directory.join(LOCK_FILENAME)).is_err()); assert_eq!(std::fs::read_to_string(directory.join(LOCK_FILENAME)).expect("invalid file must remain"), contents); } std::fs::remove_file(directory.join(LOCK_FILENAME)).expect("invalid fixture must be removed"); assert!(Lock::read(&directory).expect("missing implicit lock is empty").is_empty()); - assert!(Lock::read_file(&directory.join(LOCK_FILENAME)).is_err(), "an explicit trust file must exist"); std::fs::remove_dir_all(directory).expect("test directory must be removed"); } @@ -451,31 +470,46 @@ fn legacy_git_lock_is_readable_and_network_pins_survive_git_updates() { } #[test] -fn package_requires_a_pin_for_each_transitive_network_import() { +fn package_automatically_locks_transitive_network_imports() { leo_span::create_session_if_not_set_then(|_| { let base = unique_dir("network-transitive-pins"); let home = base.join("home"); let consumer = base.join("consumer"); let parent = format!("import token.aleo;\n{}", TRUSTED_TOKEN.replace("token.aleo", "parent.aleo")); - write_consumer(&consumer, r#"{"name":"parent.aleo","location":"network","edition":0}"#); - write_file(&home.join("registry/testnet/parent/0/parent.aleo"), &parent); - write_file(&home.join("registry/testnet/token/0/token.aleo"), TRUSTED_TOKEN); - write_network_lock(&consumer, &[network_pin(&parent, 0)]); - let error = Package::from_directory( + write_consumer(&consumer, r#"{"name":"parent.aleo","location":"network"}"#); + write_file(&home.join("registry/testnet/parent/99/parent.aleo"), &parent); + write_file(&home.join("registry/testnet/token/99/token.aleo"), TRUSTED_TOKEN); + let (endpoint, server) = network_response(&[ + ("/testnet/program/parent.aleo/latest_edition", "200 OK", "2"), + ("/testnet/program/parent.aleo/2", "200 OK", &parent), + ("/testnet/program/token.aleo/latest_edition", "200 OK", "1"), + ("/testnet/program/token.aleo/1", "200 OK", TRUSTED_TOKEN), + ]); + let package = Package::from_directory( &consumer, &home, false, false, false, Some(leo_ast::NetworkName::TestnetV0), - Some("http://127.0.0.1:1"), + Some(&endpoint), 0, ) - .expect_err("a pinned parent must not confer trust on its imports"); - assert!(error.to_string().contains("token.aleo"), "{error}"); - assert!(error.to_string().contains("pin"), "{error}"); - write_network_lock(&consumer, &[network_pin(&parent, 0), network_pin(TRUSTED_TOKEN, 0)]); - let package = Package::from_directory( + .expect("first build must fetch and lock every transitive dependency"); + assert_eq!(server.join().expect("fixture must finish").len(), 4); + let names: Vec<_> = package.compilation_units.iter().map(|unit| unit.name.to_string()).collect(); + assert_eq!(names, ["token.aleo", "parent.aleo", "consumer.aleo"]); + let lock = Lock::read(&consumer).expect("automatic lock must exist"); + lock.network_pin("parent.aleo", leo_ast::NetworkName::TestnetV0, Some(2)) + .expect("parent pin must exist") + .verify(&parent) + .expect("parent checksum must match"); + lock.network_pin("token.aleo", leo_ast::NetworkName::TestnetV0, Some(1)) + .expect("transitive pin must exist") + .verify(TRUSTED_TOKEN) + .expect("transitive checksum must match"); + let original_lock = std::fs::read(consumer.join(LOCK_FILENAME)).expect("lock must exist"); + Package::from_directory( &consumer, &home, false, @@ -485,31 +519,49 @@ fn package_requires_a_pin_for_each_transitive_network_import() { Some("http://127.0.0.1:1"), 0, ) - .expect("each pinned transitive dependency must load offline from cache"); - assert_eq!(package.compilation_units.len(), 3); - let names: Vec<_> = package.compilation_units.iter().map(|unit| unit.name.to_string()).collect(); - assert_eq!(names, ["token.aleo", "parent.aleo", "consumer.aleo"]); + .expect("repeat build must use locked editions without consulting the endpoint"); + let (endpoint, server) = network_response(&[ + ("/testnet/program/parent.aleo/2", "200 OK", &parent), + ("/testnet/program/token.aleo/1", "200 OK", TRUSTED_TOKEN), + ]); + Package::from_directory( + &consumer, + &home, + true, + false, + false, + Some(leo_ast::NetworkName::TestnetV0), + Some(&endpoint), + 0, + ) + .expect("no-cache must fetch locked editions and retain their checksums"); + assert_eq!(server.join().expect("fixture must finish").len(), 2); + assert_eq!(std::fs::read(consumer.join(LOCK_FILENAME)).expect("lock must remain"), original_lock); let local = base.join("local"); write_program(&local, "local.aleo", "null"); let package = Package::from_directory(&local, &home, false, false, false, None, None, 0) .expect("a local-only package must not require network pins"); assert_eq!(package.compilation_units.len(), 1); + assert!(!local.join(LOCK_FILENAME).exists()); std::fs::remove_dir_all(base).expect("test directory must be removed"); }); } #[test] -fn package_uses_workspace_pins_and_keeps_explicit_override_read_only() { +fn package_keeps_workspace_and_development_network_pins() { leo_span::create_session_if_not_set_then(|_| { - let base = unique_dir("network-workspace-override"); + let base = unique_dir("network-workspace-pins"); let home = base.join("home"); let workspace = base.join("workspace"); let consumer = workspace.join("consumer"); write_file(&workspace.join(WORKSPACE_MANIFEST_FILENAME), r#"{"members":["consumer"]}"#); write_consumer(&consumer, r#"{"name":"token.aleo","location":"network","edition":0}"#); - write_file(&home.join("registry/testnet/token/0/token.aleo"), TRUSTED_TOKEN); - write_network_lock(&workspace, &[network_pin(TRUSTED_TOKEN, 0)]); + std::fs::create_dir_all(&home).expect("home must exist"); + let sibling = TRUSTED_TOKEN.replace("token.aleo", "sibling.aleo"); + let dev = TRUSTED_TOKEN.replace("token.aleo", "dev.aleo"); + write_network_lock(&workspace, &[network_pin(&sibling, 1), network_pin(&dev, 2)]); write_file(&consumer.join(LOCK_FILENAME), "ignored member lock"); + let (endpoint, server) = network_response(&[("/testnet/program/token.aleo/0", "200 OK", TRUSTED_TOKEN)]); Package::from_directory( &consumer, &home, @@ -517,57 +569,216 @@ fn package_uses_workspace_pins_and_keeps_explicit_override_read_only() { false, false, Some(leo_ast::NetworkName::TestnetV0), - Some("http://127.0.0.1:1"), + Some(&endpoint), 0, ) - .expect("workspace root must supply network trust"); + .expect("workspace root must receive newly resolved network dependencies"); + assert_eq!(server.join().expect("fixture must finish").len(), 1); + let lock = Lock::read(&workspace).expect("workspace lock must exist"); + for (name, edition) in [("token.aleo", 0), ("sibling.aleo", 1), ("dev.aleo", 2)] { + assert!(lock.network_pin(name, leo_ast::NetworkName::TestnetV0, Some(edition)).is_some()); + } assert_eq!( std::fs::read_to_string(consumer.join(LOCK_FILENAME)).expect("member file must remain"), "ignored member lock" ); - let approved = base.join("approved"); - write_network_lock(&approved, &[network_pin(TRUSTED_TOKEN, 0)]); - let override_path = approved.join(LOCK_FILENAME); - let override_contents = std::fs::read(&override_path).expect("approved file must exist"); - let conflicting = TRUSTED_TOKEN.replace("private", "public"); - write_network_lock(&workspace, &[network_pin(&conflicting, 0)]); - assert!( - Package::from_directory( + let original = std::fs::read(workspace.join(LOCK_FILENAME)).expect("workspace lock must exist"); + write_file(&home.join("registry/testnet/token/0/token.aleo"), &TRUSTED_TOKEN.replace("private", "public")); + let error = Package::from_directory( + &consumer, + &home, + false, + false, + false, + Some(leo_ast::NetworkName::TestnetV0), + Some("http://127.0.0.1:1"), + 0, + ) + .expect_err("a changed cached dependency must stop the build"); + assert!(error.to_string().contains("checksum"), "{error}"); + assert_eq!(std::fs::read(workspace.join(LOCK_FILENAME)).expect("workspace lock must remain"), original); + std::fs::remove_dir_all(base).expect("test directory must be removed"); + }); +} + +#[test] +fn invalid_first_response_does_not_create_a_pin_or_cache() { + leo_span::create_session_if_not_set_then(|_| { + for bytecode in [ + TRUSTED_TOKEN.replace("token.aleo", "other.aleo"), + "invalid Aleo".to_owned(), + " ".repeat(crate::MAX_PROGRAM_SIZE + 1), + ] { + let home = unique_dir("network-invalid-first-response"); + let mut lock = Lock::default(); + let (endpoint, server) = network_response(&[("/testnet/program/token.aleo/0", "200 OK", &bytecode)]); + assert!( + crate::CompilationUnit::fetch( + Symbol::intern("token.aleo"), + Some(0), + &home, + leo_ast::NetworkName::TestnetV0, + &endpoint, + false, + 0, + &mut lock, + ) + .is_err() + ); + assert_eq!(server.join().expect("fixture must finish").len(), 1); + assert!(lock.is_empty(), "invalid first response must not establish a checksum"); + assert!(!home.join("registry").exists()); + std::fs::remove_dir_all(home).expect("test directory must be removed"); + } + }); +} + +#[test] +fn failed_network_graph_does_not_commit_new_pins() { + leo_span::create_session_if_not_set_then(|_| { + for existing_lock in [false, true] { + let base = unique_dir("network-failed-graph"); + let home = base.join("home"); + let consumer = base.join("consumer"); + std::fs::create_dir_all(&home).expect("home must exist"); + write_consumer(&consumer, r#"{"name":"parent.aleo","location":"network"}"#); + let original = if existing_lock { + write_network_lock(&consumer, &[network_pin(&TRUSTED_TOKEN.replace("token.aleo", "other.aleo"), 1)]); + Some(std::fs::read(consumer.join(LOCK_FILENAME)).expect("old lock must exist")) + } else { + None + }; + let parent = format!("import token.aleo;\n{}", TRUSTED_TOKEN.replace("token.aleo", "parent.aleo")); + let token = format!("import parent.aleo;\n{TRUSTED_TOKEN}"); + let (endpoint, server) = network_response(&[ + ("/testnet/program/parent.aleo/latest_edition", "200 OK", "0"), + ("/testnet/program/parent.aleo/0", "200 OK", &parent), + ("/testnet/program/token.aleo/latest_edition", "200 OK", "0"), + ("/testnet/program/token.aleo/0", "200 OK", &token), + ]); + let error = Package::from_directory( &consumer, &home, false, false, false, Some(leo_ast::NetworkName::TestnetV0), - Some("http://127.0.0.1:1"), + Some(&endpoint), 0, ) - .is_err(), - "the workspace pin must reject a different checksum" - ); - Package::from_directory_with_network_lock( - &consumer, + .expect_err("a circular dependency graph must fail after resolution"); + assert!(error.to_string().contains("circular"), "{error}"); + assert_eq!(server.join().expect("fixture must finish").len(), 4); + assert_eq!(std::fs::read(consumer.join(LOCK_FILENAME)).ok(), original); + std::fs::remove_dir_all(base).expect("test directory must be removed"); + } + }); +} + +#[test] +fn standalone_bytecode_resolves_imports_without_creating_a_lock_file() { + leo_span::create_session_if_not_set_then(|_| { + let base = unique_dir("network-standalone"); + let home = base.join("home"); + std::fs::create_dir_all(&home).expect("home must exist"); + let path = base.join("parent.aleo"); + write_file(&path, &format!("import token.aleo;\n{}", TRUSTED_TOKEN.replace("token.aleo", "parent.aleo"))); + let (endpoint, server) = network_response(&[ + ("/testnet/program/token.aleo/latest_edition", "200 OK", "0"), + ("/testnet/program/token.aleo/0", "200 OK", TRUSTED_TOKEN), + ]); + let package = Package::from_aleo_file( + &path, &home, + None, + false, false, + Some(leo_ast::NetworkName::TestnetV0), + Some(&endpoint), + 0, + ) + .expect("standalone bytecode imports must resolve without manual pins"); + assert_eq!(package.compilation_units.len(), 2); + assert_eq!(server.join().expect("fixture must finish").len(), 2); + assert!(!base.join(LOCK_FILENAME).exists(), "standalone loading must not create a project lock"); + write_program(&base, "parent.aleo", "null"); + write_network_lock(&base, &[network_pin(TRUSTED_TOKEN, 0)]); + let original = std::fs::read(base.join(LOCK_FILENAME)).expect("project lock must exist"); + let nested_path = base.join("build/parent/parent.aleo"); + write_file(&nested_path, &std::fs::read_to_string(&path).expect("bytecode fixture must exist")); + Package::from_aleo_file( + &nested_path, + &home, + None, false, false, Some(leo_ast::NetworkName::TestnetV0), Some("http://127.0.0.1:1"), 0, - Some(&override_path), ) - .expect("the explicit trust file must select the approved checksum"); - assert_eq!(std::fs::read(&override_path).expect("approved file must remain"), override_contents); - Lock::read(&workspace) - .expect("workspace lock must remain valid") - .network_pin("token.aleo", leo_ast::NetworkName::TestnetV0, Some(0)) - .expect("workspace pin must remain") - .verify(&conflicting) - .expect("the override must not replace the workspace pin"); + .expect("standalone imports must use the enclosing project lock"); + assert_eq!(std::fs::read(base.join(LOCK_FILENAME)).expect("project lock must remain"), original); + assert!(!base.join("build/parent/leo.lock").exists()); std::fs::remove_dir_all(base).expect("test directory must be removed"); }); } +#[cfg(unix)] +#[test] +fn lock_write_preserves_existing_permissions() { + use std::os::unix::fs::PermissionsExt; + + let dir = unique_dir("lock-permissions"); + let path = dir.join(LOCK_FILENAME); + let mut lock = Lock::default(); + lock.record("foo".into(), "url".into(), "default".into(), "abc123".into()); + lock.write(&dir).expect("write initial lock"); + for mode in [0o600, 0o640] { + std::fs::set_permissions(&path, std::fs::Permissions::from_mode(mode)).expect("set lock permissions"); + lock.write(&dir).expect("replace lock"); + assert_eq!(std::fs::metadata(&path).expect("read lock metadata").permissions().mode() & 0o777, mode); + } + std::fs::remove_dir_all(dir).expect("remove fixture"); +} + +#[cfg(unix)] +#[test] +fn lock_write_failure_preserves_existing_lock() { + const CHILD: &str = "LEO_TEST_LOCK_WRITE_LIMIT"; + if std::env::var_os(CHILD).is_none() { + let output = std::process::Command::new("bash") + .args([ + "-c", + "trap '' XFSZ; ulimit -f 1; exec \"$1\" --exact tests::lock_write_failure_preserves_existing_lock --nocapture", + "bash", + ]) + .arg(std::env::current_exe().expect("The test executable must exist.")) + .env(CHILD, "1") + .output() + .expect("The limited child process must start."); + assert!( + output.status.success(), + "The limited child test failed: {}\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + return; + } + let dir = unique_dir("lock-write-failure"); + let path = dir.join(LOCK_FILENAME); + let original = r#"{"version":1,"git":[]}"#; + write_file(&path, original); + let mut lock = Lock::default(); + lock.record("foo".into(), "x".repeat(16 * 1024), "default".into(), "abc123".into()); + let error = lock.write(&dir).expect_err("The file-size limit must reject the temporary write."); + assert!(error.to_string().contains("failed to write lock file"), "{error}"); + assert_eq!(std::fs::read_to_string(&path).expect("The previous lock must remain readable."), original); + let mut entries = std::fs::read_dir(&dir).expect("The lock directory must remain readable."); + assert_eq!(entries.next().expect("The lock must remain.").expect("The entry must be readable.").path(), path); + assert!(entries.next().is_none(), "The failed write must remove its temporary file."); + std::fs::remove_dir_all(dir).expect("The test directory must be removed."); +} + // Reference resolution (`crate::git::resolve`). #[test] diff --git a/tests/expectations/cli/network_dependency/COMMANDS b/tests/expectations/cli/network_dependency/COMMANDS index 9dec5105357..a5715d7e6a3 100755 --- a/tests/expectations/cli/network_dependency/COMMANDS +++ b/tests/expectations/cli/network_dependency/COMMANDS @@ -1,35 +1,11 @@ #!/usr/bin/env bash +set -e + LEO_BIN=${1} -# These checksums come from the reviewed, locally compiled bytecode in this fixture's expectations. -NETWORK_LOCK=$(mktemp) -trap 'rm -f "$NETWORK_LOCK"' EXIT -cat > "$NETWORK_LOCK" <<'JSON' -{"version": 2, "git": [], "network": [ - { - "name": "test_program1.aleo", - "network": "testnet", - "edition": 0, - "checksum": [ - 192, 225, 38, 151, 139, 155, 148, 209, 105, 110, 246, 83, 90, 243, 78, 35, - 155, 186, 242, 17, 196, 169, 179, 144, 71, 227, 34, 207, 121, 158, 34, 25 - ] - }, - { - "name": "test_program2.aleo", - "network": "testnet", - "edition": 0, - "checksum": [ - 206, 255, 111, 48, 59, 85, 122, 166, 182, 205, 10, 141, 58, 23, 70, 232, - 105, 221, 83, 216, 186, 14, 207, 24, 13, 221, 236, 0, 143, 199, 151, 2 - ] - } -]} -JSON PRIVATE_KEY="APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH" COMMON_OPTS="-y --disable-update-check --broadcast --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" -COMMON_OPTS="--network-lock $NETWORK_LOCK $COMMON_OPTS" cd test_program1 || exit 1 echo "DEPLOYMENT 1" @@ -39,5 +15,18 @@ cd ../test_program2 || exit 1 echo "DEPLOYMENT 2" $LEO_BIN deploy $COMMON_OPTS +# The first dependency resolution writes the checksum without a supplied lock. +grep -Fq '"name": "test_program1.aleo"' leo.lock +grep -Fq '"network": "testnet"' leo.lock +grep -Fq '"edition": 0' leo.lock +grep -Fq '"checksum": [' leo.lock +LOCK_COPY=$(mktemp) +trap 'rm -f "$LOCK_COPY"' EXIT +cp leo.lock "$LOCK_COPY" + echo "EXECUTION" $LEO_BIN execute test_program2.aleo/main $COMMON_OPTS + +# Resolving the same dependency preserves the lock. +cmp leo.lock "$LOCK_COPY" +rm leo.lock diff --git a/tests/expectations/cli/test_add_dependency/STDERR b/tests/expectations/cli/test_add_dependency/STDERR index c7b00170ee4..64d14671d04 100644 --- a/tests/expectations/cli/test_add_dependency/STDERR +++ b/tests/expectations/cli/test_add_dependency/STDERR @@ -22,9 +22,9 @@ Error [ECLI0377045]: Could not read `../credits/program.json` — is `../credits --- 7. leo add --dev for a program not on the network (should fail) --- Leo ⚠️ No network specified, defaulting to 'testnet'. -Error [ECLI0377045]: Could not find program `dev_dep.aleo` on network `testnet` at `http://localhost:3030`: Error [EPAK0375085]: cannot trust network program `dev_dep.aleo`: no trusted checksum pin for this network +Error [ECLI0377045]: Could not find program `dev_dep.aleo` on network `testnet` at `http://localhost:3030`: Error [EPAK0375067]: network request to `http://localhost:3030/testnet/program/dev_dep.aleo/latest_edition` failed with status `500 Internal Server Error` | - = Add the program's network, edition, and checksum from reviewed bytecode or an independent trusted source to `leo.lock`. Use `--network-lock ` to select a trusted lock file. + = Verify that `--network` and `--endpoint` point to a running node and that you have connectivity. --- 8. leo add --dev for a program on the network (should succeed) --- Leo ⚠️ No network specified, defaulting to 'testnet'. diff --git a/tests/expectations/cli/test_deploy/COMMANDS b/tests/expectations/cli/test_deploy/COMMANDS index 0ddd62a30dc..b0ee888ed88 100755 --- a/tests/expectations/cli/test_deploy/COMMANDS +++ b/tests/expectations/cli/test_deploy/COMMANDS @@ -4,92 +4,85 @@ set -e LEO_BIN=${1} -# This checksum comes from the reviewed, locally compiled bytecode in this fixture's expectations. -REJECTED=$(mktemp -d) -NETWORK_LOCK="$REJECTED/trusted.lock" -trap 'rm -rf "$REJECTED"' EXIT -cat > "$NETWORK_LOCK" <<'JSON' -{"version": 2, "git": [], "network": [ - { - "name": "some_sample_leo_program.aleo", - "network": "testnet", - "edition": 0, - "checksum": [ - 218, 29, 76, 10, 129, 17, 226, 52, 243, 166, 142, 129, 90, 12, 147, 224, - 200, 219, 166, 88, 40, 193, 250, 177, 40, 42, 100, 97, 232, 64, 198, 156 - ] - } -]} -JSON +REMOTE=$(mktemp -d) +trap 'rm -rf "$REMOTE"' EXIT PRIVATE_KEY="APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH" COMMON_OPTS="--disable-update-check --broadcast --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" -COMMON_OPTS="--network-lock $NETWORK_LOCK $COMMON_OPTS" ALEO_FILE="./build/some_sample_leo_program/some_sample_leo_program.aleo" $LEO_BIN build --disable-update-check --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} $LEO_BIN --path $ALEO_FILE deploy -y $COMMON_OPTS -# An explicit untrusted lock must override an otherwise trusted implicit lock. -mkdir "$REJECTED/remote" "$REJECTED/home" -cp "$NETWORK_LOCK" "$REJECTED/remote/leo.lock" -printf '{"version":2,"git":[],"network":[]}' > "$REJECTED/missing.lock" -sed 's/218, 29/0, 29/' "$NETWORK_LOCK" > "$REJECTED/mismatch.lock" -cp "$ALEO_FILE" "$REJECTED/original.aleo" -for trust in missing mismatch; do - if [ "$trust" = missing ]; then - diagnostic="no trusted checksum pin" +# Remote commands work from an empty directory without a supplied lock. +mkdir "$REMOTE/empty" "$REMOTE/home" +for command in run execute synthesize; do + case "$command" in + run) + args=(run some_sample_leo_program.aleo::main 1u32 2u32 --no-cache --private-key "$PRIVATE_KEY") + ;; + execute) + args=(execute some_sample_leo_program.aleo::main 1u32 2u32 --no-cache --yes --skip-execute-proof --private-key "$PRIVATE_KEY" --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20) + ;; + synthesize) + args=(synthesize some_sample_leo_program.aleo --skip mint --skip dynamic_transfer) + ;; + esac + if ! "$LEO_BIN" --disable-update-check --path "$REMOTE/empty" --home "$REMOTE/home" "${args[@]}" \ + --network testnet --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" > "$REMOTE/output" 2>&1; then + cat "$REMOTE/output" + exit 1 + fi + if [ "$command" != synthesize ]; then + grep -Fq '3u32' "$REMOTE/output" else - diagnostic="bytecode does not match the trusted checksum" + grep -Fq 'main' "$REMOTE/output" fi - for command in upgrade run execute synthesize; do - case "$command" in - upgrade) - args=(--path "$ALEO_FILE" upgrade --no-local --no-cache --yes --broadcast --print --save "$REJECTED/released") - ;; - run) - args=(--path "$REJECTED/remote" run some_sample_leo_program.aleo::main 1u32 2u32 --no-cache) - ;; - execute) - args=(--path "$REJECTED/remote" execute some_sample_leo_program.aleo::main 1u32 2u32 --no-cache --yes --broadcast --print --save "$REJECTED/released") - ;; - synthesize) - args=(--path "$REJECTED/remote" synthesize some_sample_leo_program.aleo --save "$REJECTED/released") - ;; - esac - if [ "$command" != synthesize ]; then - args+=(--private-key "$PRIVATE_KEY") - fi - if "$LEO_BIN" --disable-update-check --home "$REJECTED/home" --network-lock "$REJECTED/$trust.lock" "${args[@]}" \ - --network testnet --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" \ - > "$REJECTED/output" 2>&1; then - cat "$REJECTED/output" - echo "$command accepted $trust trust" >&2 - exit 1 - fi - if ! grep -Fq "$diagnostic" "$REJECTED/output"; then - cat "$REJECTED/output" - exit 1 - fi - test ! -e "$REJECTED/released" - test ! -e "$REJECTED/home/registry" - if grep -Eq 'at1[[:alnum:]]+|Broadcasting|transaction_id|Proving' "$REJECTED/output"; then - cat "$REJECTED/output" - exit 1 - fi - cmp "$ALEO_FILE" "$REJECTED/original.aleo" - cmp "$NETWORK_LOCK" "$REJECTED/remote/leo.lock" - done + test ! -e "$REMOTE/empty/leo.lock" done -# The rejected upgrade must leave the deployed edition unchanged. -test "$(curl -fsS "http://localhost:${LEO_DEVNODE_PORT:-3030}/testnet/program/some_sample_leo_program.aleo/latest_edition")" = 0 -# The same remote dispatch succeeds with the reviewed pin. -if ! "$LEO_BIN" --disable-update-check --path "$REJECTED/remote" --home "$REJECTED/home" --network-lock "$NETWORK_LOCK" \ - run some_sample_leo_program.aleo::main 1u32 2u32 --no-cache --network testnet \ - --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" --private-key "$PRIVATE_KEY" > "$REJECTED/output" 2>&1; then - cat "$REJECTED/output" + +# An extra remote program also needs no hand-written pin. +sed 's/some_sample_leo_program/local_probe/g' "$ALEO_FILE" > "$REMOTE/empty/local_probe.aleo" +if ! "$LEO_BIN" --disable-update-check --path "$REMOTE/empty/local_probe.aleo" --home "$REMOTE/home" \ + run main 1u32 2u32 --with some_sample_leo_program.aleo --network testnet \ + --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" --private-key "$PRIVATE_KEY" > "$REMOTE/output" 2>&1; then + cat "$REMOTE/output" + exit 1 +fi +grep -Fq 'Fetching remote program some_sample_leo_program.aleo' "$REMOTE/output" +grep -Fq '3u32' "$REMOTE/output" +test ! -e "$REMOTE/empty/leo.lock" +echo "Remote run, execute, synthesize, and --with work without a supplied lock." + +# Keep an automatically generated dependency lock at edition zero for the upgrade check. +"$LEO_BIN" --disable-update-check --path "$REMOTE/lock_consumer" new lock_consumer > "$REMOTE/output" 2>&1 +if ! "$LEO_BIN" --disable-update-check --path "$REMOTE/lock_consumer" --home "$REMOTE/home" \ + add some_sample_leo_program --edition 0 --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" \ + > "$REMOTE/output" 2>&1; then + cat "$REMOTE/output" exit 1 fi -grep -Fq '3u32' "$REJECTED/output" -echo "Rejected missing and mismatched pins for upgrade, run, execute, and synthesize." +grep -Fq '"edition": 0' "$REMOTE/lock_consumer/leo.lock" +cp "$REMOTE/lock_consumer/leo.lock" "$REMOTE/original.lock" +cp "$ALEO_FILE" "$REMOTE/lock_consumer/upgrade.aleo" $LEO_BIN --path $ALEO_FILE upgrade --no-local -y $COMMON_OPTS +# A stale dependency pin must not make an upgrade validate against an old root edition. +if ! "$LEO_BIN" --path "$REMOTE/lock_consumer/upgrade.aleo" --home "$REMOTE/home" \ + upgrade --no-local -y $COMMON_OPTS > "$REMOTE/output" 2>&1; then + cat "$REMOTE/output" + exit 1 +fi +grep -Fq 'Upgrade confirmed!' "$REMOTE/output" +test "$(curl -fsS "http://localhost:${LEO_DEVNODE_PORT:-3030}/testnet/program/some_sample_leo_program.aleo/latest_edition")" = 2 +cmp "$REMOTE/lock_consumer/leo.lock" "$REMOTE/original.lock" + +# An explicit edition change updates the generated dependency lock. +if ! "$LEO_BIN" --disable-update-check --path "$REMOTE/lock_consumer" --home "$REMOTE/home" \ + add some_sample_leo_program --edition 2 --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" \ + > "$REMOTE/output" 2>&1; then + cat "$REMOTE/output" + exit 1 +fi +grep -Fq '"edition": 2' "$REMOTE/lock_consumer/leo.lock" +echo "Upgrade uses the current root edition, and an explicit dependency update replaces the lock." + $LEO_BIN --path $ALEO_FILE execute -y main 1u32 2u32 $COMMON_OPTS diff --git a/tests/expectations/cli/test_deploy/STDOUT b/tests/expectations/cli/test_deploy/STDOUT index 1c73c50ea24..e1f9d0fc350 100644 --- a/tests/expectations/cli/test_deploy/STDOUT +++ b/tests/expectations/cli/test_deploy/STDOUT @@ -80,7 +80,7 @@ Once it is deployed, it CANNOT be changed. Explored XXXXXX blocks. Transaction accepted. ✅ Deployment confirmed! -Rejected missing and mismatched pins for upgrade, run, execute, and synthesize. +Remote run, execute, synthesize, and --with work without a supplied lock. 📢 Using the following consensus heights: 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 To override, pass in `--consensus-heights` or override the environment variable `CONSENSUS_VERSION_HEIGHTS`. @@ -153,6 +153,7 @@ Loaded the following programs into the VM: Explored XXXXXX blocks. Transaction accepted. ✅ Upgrade confirmed! +Upgrade uses the current root edition, and an explicit dependency update replaces the lock. 📢 Using the following consensus heights: 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 To override, pass in `--consensus-heights` or override the environment variable `CONSENSUS_VERSION_HEIGHTS`. diff --git a/tests/expectations/cli/test_deploy_rename/COMMANDS b/tests/expectations/cli/test_deploy_rename/COMMANDS index 1643cad16b6..4080c9ed57d 100755 --- a/tests/expectations/cli/test_deploy_rename/COMMANDS +++ b/tests/expectations/cli/test_deploy_rename/COMMANDS @@ -2,26 +2,9 @@ LEO_BIN=${1} -# These checksums come from the reviewed, locally compiled bytecode in this fixture's expectations. -NETWORK_LOCK=$(mktemp) -trap 'rm -f "$NETWORK_LOCK"' EXIT -cat > "$NETWORK_LOCK" <<'JSON' -{"version": 2, "git": [], "network": [ - { - "name": "renamed_prog.aleo", - "network": "testnet", - "edition": 0, - "checksum": [ - 160, 166, 60, 22, 228, 140, 165, 241, 171, 140, 243, 102, 94, 227, 44, 139, - 3, 205, 179, 211, 213, 124, 14, 113, 28, 135, 160, 183, 198, 61, 226, 215 - ] - } -]} -JSON PRIVATE_KEY="APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH" ENDPOINT="http://localhost:${LEO_DEVNODE_PORT:-3030}" COMMON_OPTS="--disable-update-check --network testnet --endpoint $ENDPOINT --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" -COMMON_OPTS="--network-lock $NETWORK_LOCK $COMMON_OPTS" # The package declares `original_prog.aleo`, but `--rename` recompiles and deploys # it on-chain under a different identity. diff --git a/tests/expectations/cli/test_json/COMMANDS b/tests/expectations/cli/test_json/COMMANDS index 5617ace4f15..fe1038d7f70 100755 --- a/tests/expectations/cli/test_json/COMMANDS +++ b/tests/expectations/cli/test_json/COMMANDS @@ -2,25 +2,8 @@ LEO_BIN=${1} -# This checksum comes from the reviewed, locally compiled bytecode in this fixture's expectations. -NETWORK_LOCK=$(mktemp) -trap 'rm -f "$NETWORK_LOCK"' EXIT -cat > "$NETWORK_LOCK" <<'JSON' -{"version": 2, "git": [], "network": [ - { - "name": "test_json_program.aleo", - "network": "testnet", - "edition": 0, - "checksum": [ - 99, 220, 60, 163, 190, 204, 85, 96, 196, 10, 58, 203, 48, 123, 58, 247, - 226, 60, 107, 144, 47, 213, 35, 185, 180, 177, 90, 118, 89, 106, 199, 183 - ] - } -]} -JSON PRIVATE_KEY="APrivateKey1zkp2RWGDcde3efb89rjhME1VYA8QMxcxep5DShNBR6n8Yjh" COMMON_OPTS="--disable-update-check --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" -COMMON_OPTS="--network-lock $NETWORK_LOCK $COMMON_OPTS" # `synthesize` and `query` don't sign or set consensus, so they only take the connection options. CONN_OPTS="--disable-update-check --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030}" diff --git a/tests/tests/cli/network_dependency/COMMANDS b/tests/tests/cli/network_dependency/COMMANDS index 9dec5105357..a5715d7e6a3 100755 --- a/tests/tests/cli/network_dependency/COMMANDS +++ b/tests/tests/cli/network_dependency/COMMANDS @@ -1,35 +1,11 @@ #!/usr/bin/env bash +set -e + LEO_BIN=${1} -# These checksums come from the reviewed, locally compiled bytecode in this fixture's expectations. -NETWORK_LOCK=$(mktemp) -trap 'rm -f "$NETWORK_LOCK"' EXIT -cat > "$NETWORK_LOCK" <<'JSON' -{"version": 2, "git": [], "network": [ - { - "name": "test_program1.aleo", - "network": "testnet", - "edition": 0, - "checksum": [ - 192, 225, 38, 151, 139, 155, 148, 209, 105, 110, 246, 83, 90, 243, 78, 35, - 155, 186, 242, 17, 196, 169, 179, 144, 71, 227, 34, 207, 121, 158, 34, 25 - ] - }, - { - "name": "test_program2.aleo", - "network": "testnet", - "edition": 0, - "checksum": [ - 206, 255, 111, 48, 59, 85, 122, 166, 182, 205, 10, 141, 58, 23, 70, 232, - 105, 221, 83, 216, 186, 14, 207, 24, 13, 221, 236, 0, 143, 199, 151, 2 - ] - } -]} -JSON PRIVATE_KEY="APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH" COMMON_OPTS="-y --disable-update-check --broadcast --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" -COMMON_OPTS="--network-lock $NETWORK_LOCK $COMMON_OPTS" cd test_program1 || exit 1 echo "DEPLOYMENT 1" @@ -39,5 +15,18 @@ cd ../test_program2 || exit 1 echo "DEPLOYMENT 2" $LEO_BIN deploy $COMMON_OPTS +# The first dependency resolution writes the checksum without a supplied lock. +grep -Fq '"name": "test_program1.aleo"' leo.lock +grep -Fq '"network": "testnet"' leo.lock +grep -Fq '"edition": 0' leo.lock +grep -Fq '"checksum": [' leo.lock +LOCK_COPY=$(mktemp) +trap 'rm -f "$LOCK_COPY"' EXIT +cp leo.lock "$LOCK_COPY" + echo "EXECUTION" $LEO_BIN execute test_program2.aleo/main $COMMON_OPTS + +# Resolving the same dependency preserves the lock. +cmp leo.lock "$LOCK_COPY" +rm leo.lock diff --git a/tests/tests/cli/test_deploy/COMMANDS b/tests/tests/cli/test_deploy/COMMANDS index 0ddd62a30dc..b0ee888ed88 100755 --- a/tests/tests/cli/test_deploy/COMMANDS +++ b/tests/tests/cli/test_deploy/COMMANDS @@ -4,92 +4,85 @@ set -e LEO_BIN=${1} -# This checksum comes from the reviewed, locally compiled bytecode in this fixture's expectations. -REJECTED=$(mktemp -d) -NETWORK_LOCK="$REJECTED/trusted.lock" -trap 'rm -rf "$REJECTED"' EXIT -cat > "$NETWORK_LOCK" <<'JSON' -{"version": 2, "git": [], "network": [ - { - "name": "some_sample_leo_program.aleo", - "network": "testnet", - "edition": 0, - "checksum": [ - 218, 29, 76, 10, 129, 17, 226, 52, 243, 166, 142, 129, 90, 12, 147, 224, - 200, 219, 166, 88, 40, 193, 250, 177, 40, 42, 100, 97, 232, 64, 198, 156 - ] - } -]} -JSON +REMOTE=$(mktemp -d) +trap 'rm -rf "$REMOTE"' EXIT PRIVATE_KEY="APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH" COMMON_OPTS="--disable-update-check --broadcast --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" -COMMON_OPTS="--network-lock $NETWORK_LOCK $COMMON_OPTS" ALEO_FILE="./build/some_sample_leo_program/some_sample_leo_program.aleo" $LEO_BIN build --disable-update-check --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} $LEO_BIN --path $ALEO_FILE deploy -y $COMMON_OPTS -# An explicit untrusted lock must override an otherwise trusted implicit lock. -mkdir "$REJECTED/remote" "$REJECTED/home" -cp "$NETWORK_LOCK" "$REJECTED/remote/leo.lock" -printf '{"version":2,"git":[],"network":[]}' > "$REJECTED/missing.lock" -sed 's/218, 29/0, 29/' "$NETWORK_LOCK" > "$REJECTED/mismatch.lock" -cp "$ALEO_FILE" "$REJECTED/original.aleo" -for trust in missing mismatch; do - if [ "$trust" = missing ]; then - diagnostic="no trusted checksum pin" +# Remote commands work from an empty directory without a supplied lock. +mkdir "$REMOTE/empty" "$REMOTE/home" +for command in run execute synthesize; do + case "$command" in + run) + args=(run some_sample_leo_program.aleo::main 1u32 2u32 --no-cache --private-key "$PRIVATE_KEY") + ;; + execute) + args=(execute some_sample_leo_program.aleo::main 1u32 2u32 --no-cache --yes --skip-execute-proof --private-key "$PRIVATE_KEY" --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20) + ;; + synthesize) + args=(synthesize some_sample_leo_program.aleo --skip mint --skip dynamic_transfer) + ;; + esac + if ! "$LEO_BIN" --disable-update-check --path "$REMOTE/empty" --home "$REMOTE/home" "${args[@]}" \ + --network testnet --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" > "$REMOTE/output" 2>&1; then + cat "$REMOTE/output" + exit 1 + fi + if [ "$command" != synthesize ]; then + grep -Fq '3u32' "$REMOTE/output" else - diagnostic="bytecode does not match the trusted checksum" + grep -Fq 'main' "$REMOTE/output" fi - for command in upgrade run execute synthesize; do - case "$command" in - upgrade) - args=(--path "$ALEO_FILE" upgrade --no-local --no-cache --yes --broadcast --print --save "$REJECTED/released") - ;; - run) - args=(--path "$REJECTED/remote" run some_sample_leo_program.aleo::main 1u32 2u32 --no-cache) - ;; - execute) - args=(--path "$REJECTED/remote" execute some_sample_leo_program.aleo::main 1u32 2u32 --no-cache --yes --broadcast --print --save "$REJECTED/released") - ;; - synthesize) - args=(--path "$REJECTED/remote" synthesize some_sample_leo_program.aleo --save "$REJECTED/released") - ;; - esac - if [ "$command" != synthesize ]; then - args+=(--private-key "$PRIVATE_KEY") - fi - if "$LEO_BIN" --disable-update-check --home "$REJECTED/home" --network-lock "$REJECTED/$trust.lock" "${args[@]}" \ - --network testnet --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" \ - > "$REJECTED/output" 2>&1; then - cat "$REJECTED/output" - echo "$command accepted $trust trust" >&2 - exit 1 - fi - if ! grep -Fq "$diagnostic" "$REJECTED/output"; then - cat "$REJECTED/output" - exit 1 - fi - test ! -e "$REJECTED/released" - test ! -e "$REJECTED/home/registry" - if grep -Eq 'at1[[:alnum:]]+|Broadcasting|transaction_id|Proving' "$REJECTED/output"; then - cat "$REJECTED/output" - exit 1 - fi - cmp "$ALEO_FILE" "$REJECTED/original.aleo" - cmp "$NETWORK_LOCK" "$REJECTED/remote/leo.lock" - done + test ! -e "$REMOTE/empty/leo.lock" done -# The rejected upgrade must leave the deployed edition unchanged. -test "$(curl -fsS "http://localhost:${LEO_DEVNODE_PORT:-3030}/testnet/program/some_sample_leo_program.aleo/latest_edition")" = 0 -# The same remote dispatch succeeds with the reviewed pin. -if ! "$LEO_BIN" --disable-update-check --path "$REJECTED/remote" --home "$REJECTED/home" --network-lock "$NETWORK_LOCK" \ - run some_sample_leo_program.aleo::main 1u32 2u32 --no-cache --network testnet \ - --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" --private-key "$PRIVATE_KEY" > "$REJECTED/output" 2>&1; then - cat "$REJECTED/output" + +# An extra remote program also needs no hand-written pin. +sed 's/some_sample_leo_program/local_probe/g' "$ALEO_FILE" > "$REMOTE/empty/local_probe.aleo" +if ! "$LEO_BIN" --disable-update-check --path "$REMOTE/empty/local_probe.aleo" --home "$REMOTE/home" \ + run main 1u32 2u32 --with some_sample_leo_program.aleo --network testnet \ + --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" --private-key "$PRIVATE_KEY" > "$REMOTE/output" 2>&1; then + cat "$REMOTE/output" + exit 1 +fi +grep -Fq 'Fetching remote program some_sample_leo_program.aleo' "$REMOTE/output" +grep -Fq '3u32' "$REMOTE/output" +test ! -e "$REMOTE/empty/leo.lock" +echo "Remote run, execute, synthesize, and --with work without a supplied lock." + +# Keep an automatically generated dependency lock at edition zero for the upgrade check. +"$LEO_BIN" --disable-update-check --path "$REMOTE/lock_consumer" new lock_consumer > "$REMOTE/output" 2>&1 +if ! "$LEO_BIN" --disable-update-check --path "$REMOTE/lock_consumer" --home "$REMOTE/home" \ + add some_sample_leo_program --edition 0 --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" \ + > "$REMOTE/output" 2>&1; then + cat "$REMOTE/output" exit 1 fi -grep -Fq '3u32' "$REJECTED/output" -echo "Rejected missing and mismatched pins for upgrade, run, execute, and synthesize." +grep -Fq '"edition": 0' "$REMOTE/lock_consumer/leo.lock" +cp "$REMOTE/lock_consumer/leo.lock" "$REMOTE/original.lock" +cp "$ALEO_FILE" "$REMOTE/lock_consumer/upgrade.aleo" $LEO_BIN --path $ALEO_FILE upgrade --no-local -y $COMMON_OPTS +# A stale dependency pin must not make an upgrade validate against an old root edition. +if ! "$LEO_BIN" --path "$REMOTE/lock_consumer/upgrade.aleo" --home "$REMOTE/home" \ + upgrade --no-local -y $COMMON_OPTS > "$REMOTE/output" 2>&1; then + cat "$REMOTE/output" + exit 1 +fi +grep -Fq 'Upgrade confirmed!' "$REMOTE/output" +test "$(curl -fsS "http://localhost:${LEO_DEVNODE_PORT:-3030}/testnet/program/some_sample_leo_program.aleo/latest_edition")" = 2 +cmp "$REMOTE/lock_consumer/leo.lock" "$REMOTE/original.lock" + +# An explicit edition change updates the generated dependency lock. +if ! "$LEO_BIN" --disable-update-check --path "$REMOTE/lock_consumer" --home "$REMOTE/home" \ + add some_sample_leo_program --edition 2 --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" \ + > "$REMOTE/output" 2>&1; then + cat "$REMOTE/output" + exit 1 +fi +grep -Fq '"edition": 2' "$REMOTE/lock_consumer/leo.lock" +echo "Upgrade uses the current root edition, and an explicit dependency update replaces the lock." + $LEO_BIN --path $ALEO_FILE execute -y main 1u32 2u32 $COMMON_OPTS diff --git a/tests/tests/cli/test_deploy_rename/COMMANDS b/tests/tests/cli/test_deploy_rename/COMMANDS index 1643cad16b6..4080c9ed57d 100755 --- a/tests/tests/cli/test_deploy_rename/COMMANDS +++ b/tests/tests/cli/test_deploy_rename/COMMANDS @@ -2,26 +2,9 @@ LEO_BIN=${1} -# These checksums come from the reviewed, locally compiled bytecode in this fixture's expectations. -NETWORK_LOCK=$(mktemp) -trap 'rm -f "$NETWORK_LOCK"' EXIT -cat > "$NETWORK_LOCK" <<'JSON' -{"version": 2, "git": [], "network": [ - { - "name": "renamed_prog.aleo", - "network": "testnet", - "edition": 0, - "checksum": [ - 160, 166, 60, 22, 228, 140, 165, 241, 171, 140, 243, 102, 94, 227, 44, 139, - 3, 205, 179, 211, 213, 124, 14, 113, 28, 135, 160, 183, 198, 61, 226, 215 - ] - } -]} -JSON PRIVATE_KEY="APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH" ENDPOINT="http://localhost:${LEO_DEVNODE_PORT:-3030}" COMMON_OPTS="--disable-update-check --network testnet --endpoint $ENDPOINT --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" -COMMON_OPTS="--network-lock $NETWORK_LOCK $COMMON_OPTS" # The package declares `original_prog.aleo`, but `--rename` recompiles and deploys # it on-chain under a different identity. diff --git a/tests/tests/cli/test_json/COMMANDS b/tests/tests/cli/test_json/COMMANDS index 5617ace4f15..fe1038d7f70 100755 --- a/tests/tests/cli/test_json/COMMANDS +++ b/tests/tests/cli/test_json/COMMANDS @@ -2,25 +2,8 @@ LEO_BIN=${1} -# This checksum comes from the reviewed, locally compiled bytecode in this fixture's expectations. -NETWORK_LOCK=$(mktemp) -trap 'rm -f "$NETWORK_LOCK"' EXIT -cat > "$NETWORK_LOCK" <<'JSON' -{"version": 2, "git": [], "network": [ - { - "name": "test_json_program.aleo", - "network": "testnet", - "edition": 0, - "checksum": [ - 99, 220, 60, 163, 190, 204, 85, 96, 196, 10, 58, 203, 48, 123, 58, 247, - 226, 60, 107, 144, 47, 213, 35, 185, 180, 177, 90, 118, 89, 106, 199, 183 - ] - } -]} -JSON PRIVATE_KEY="APrivateKey1zkp2RWGDcde3efb89rjhME1VYA8QMxcxep5DShNBR6n8Yjh" COMMON_OPTS="--disable-update-check --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" -COMMON_OPTS="--network-lock $NETWORK_LOCK $COMMON_OPTS" # `synthesize` and `query` don't sign or set consensus, so they only take the connection options. CONN_OPTS="--disable-update-check --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030}" From 255f3d3d69c30a4cc3a27e5a69a95ce3515f1e4e Mon Sep 17 00:00:00 2001 From: Mohammad Fawaz Date: Wed, 30 Sep 2026 12:41:59 -0400 Subject: [PATCH 05/10] Simplify network checksum validation and edition selection --- crates/leo/src/cli/cli.rs | 2 +- crates/leo/src/cli/commands/add.rs | 13 ++--- crates/leo/src/cli/commands/common/query.rs | 4 +- crates/package/src/compilation_unit.rs | 54 ++++++++++----------- crates/package/src/lock.rs | 39 +-------------- crates/package/src/tests.rs | 42 ++++++++-------- 6 files changed, 58 insertions(+), 96 deletions(-) diff --git a/crates/leo/src/cli/cli.rs b/crates/leo/src/cli/cli.rs index 51adba39680..d2f2183bc43 100644 --- a/crates/leo/src/cli/cli.rs +++ b/crates/leo/src/cli/cli.rs @@ -606,7 +606,7 @@ mod tests { if existing_lock { let pin = lock.network_pin("target.aleo", network, None).expect("The original pin must remain"); assert_eq!(pin.edition, 0); - pin.verify(source).expect("The original checksum must remain"); + assert_eq!(pin.checksum, program.to_checksum().map(|byte| *byte)); } else { assert!(lock.is_empty()); assert!(!project.join("leo.lock").exists()); diff --git a/crates/leo/src/cli/commands/add.rs b/crates/leo/src/cli/commands/add.rs index 491bf285c73..fa1eebef0b6 100755 --- a/crates/leo/src/cli/commands/add.rs +++ b/crates/leo/src/cli/commands/add.rs @@ -254,17 +254,14 @@ impl Command for LeoAdd { let old_lock = Lock::read(&lock_dir)?; let mut lock = old_lock.clone(); (|| { - let edition = if name == "credits.aleo" { - self.source.edition.or(Some(0)) - } else { - Some(match self.source.edition { - Some(edition) => edition, - None => leo_package::fetch_latest_edition(&name, &endpoint, network, self.network_retries)?, - }) + let edition = match self.source.edition { + Some(edition) => edition, + None if name == "credits.aleo" => 0, + None => leo_package::fetch_latest_edition(&name, &endpoint, network, self.network_retries)?, }; CompilationUnit::fetch( Symbol::intern(&name), - edition, + Some(edition), &home, network, &endpoint, diff --git a/crates/leo/src/cli/commands/common/query.rs b/crates/leo/src/cli/commands/common/query.rs index 06665d17062..ed6973a9a15 100644 --- a/crates/leo/src/cli/commands/common/query.rs +++ b/crates/leo/src/cli/commands/common/query.rs @@ -46,7 +46,7 @@ pub fn get_public_balance( }, }, } - .execute(Context { recursive: true, package_filter: None, ..context.clone() })?; + .execute(Context::new(context.path.clone(), context.home.clone(), true, None)?)?; // Remove the last 3 characters since they represent the `u64` suffix. public_balance.truncate(public_balance.len() - 3); // Make sure the balance is valid. @@ -76,7 +76,7 @@ pub fn get_latest_block_height( }, }, } - .execute(Context { recursive: true, package_filter: None, ..context.clone() })?; + .execute(Context::new(context.path.clone(), context.home.clone(), true, None)?)?; // Parse the height. let height = height.parse::().map_err(crate::errors::string_parse_error)?; Ok(height) diff --git a/crates/package/src/compilation_unit.rs b/crates/package/src/compilation_unit.rs index f649ef0f3cb..805d83fe589 100644 --- a/crates/package/src/compilation_unit.rs +++ b/crates/package/src/compilation_unit.rs @@ -280,31 +280,26 @@ impl CompilationUnit { let url = format!("{endpoint}/{network}/program/{full_name}/{edition}"); fetch_from_network(&url, network_retries)? }; - let new_pin = if let Some(pin) = pin { - pin.verify(&bytecode)?; - None - } else { - if bytecode.len() > MAX_PROGRAM_SIZE { - return Err( - crate::errors::program_size_limit_exceeded(&full_name, bytecode.len(), MAX_PROGRAM_SIZE).into() - ); - } - let program: SvmProgram = - bytecode.parse().map_err(|_| crate::errors::snarkvm_parsing_error(name))?; - if program.id().to_string() != full_name { - return Err(crate::errors::untrusted_network_program( - &full_name, - "the bytecode declares a different program ID", - ) - .into()); - } - Some(NetworkLockEntry { - name: full_name.clone(), - network: network.to_string(), - edition, - checksum: program.to_checksum().map(|byte| *byte), - }) - }; + if bytecode.len() > MAX_PROGRAM_SIZE { + return Err(crate::errors::program_size_limit_exceeded(&full_name, bytecode.len(), MAX_PROGRAM_SIZE).into()); + } + let program: SvmProgram = + bytecode.parse().map_err(|_| crate::errors::snarkvm_parsing_error(name))?; + if program.id().to_string() != full_name { + return Err(crate::errors::untrusted_network_program( + &full_name, + "the bytecode declares a different program ID", + ) + .into()); + } + let checksum = program.to_checksum().map(|byte| *byte); + if pin.is_some_and(|pin| pin.checksum != checksum) { + return Err(crate::errors::untrusted_network_program( + &full_name, + "the bytecode does not match the locked checksum", + ) + .into()); + } let dependencies = parse_dependencies_from_aleo(name, &bytecode, &IndexMap::new())?; if !use_cache { @@ -322,8 +317,13 @@ impl CompilationUnit { })?; } - if let Some(pin) = new_pin { - pins.record_network(pin); + if pin.is_none() { + pins.record_network(NetworkLockEntry { + name: full_name.clone(), + network: network.to_string(), + edition, + checksum, + }); } Ok(Self { diff --git a/crates/package/src/lock.rs b/crates/package/src/lock.rs index 68114b00059..0ad067e25eb 100644 --- a/crates/package/src/lock.rs +++ b/crates/package/src/lock.rs @@ -18,7 +18,7 @@ use leo_ast::NetworkName; use leo_errors::Result; -use snarkvm::prelude::{Program, ProgramID, TestnetV0}; +use snarkvm::prelude::{ProgramID, TestnetV0}; use indexmap::IndexSet; use serde::{Deserialize, Serialize}; @@ -50,37 +50,6 @@ pub struct NetworkLockEntry { pub checksum: [u8; 32], } -impl NetworkLockEntry { - /// Verify bytecode before it can enter the cache or dependency graph. - pub fn verify(&self, bytecode: &str) -> Result<()> { - if bytecode.len() > crate::MAX_PROGRAM_SIZE { - return Err(crate::errors::program_size_limit_exceeded( - &self.name, - bytecode.len(), - crate::MAX_PROGRAM_SIZE, - ) - .into()); - } - let program: Program = - bytecode.parse().map_err(|_| crate::errors::snarkvm_parsing_error(crate::bare_unit_name(&self.name)))?; - if program.id().to_string() != self.name { - return Err(crate::errors::untrusted_network_program( - &self.name, - "the bytecode declares a different program ID", - ) - .into()); - } - if program.to_checksum().map(|byte| *byte) != self.checksum { - return Err(crate::errors::untrusted_network_program( - &self.name, - "the bytecode does not match the locked checksum", - ) - .into()); - } - Ok(()) - } -} - /// The contents of `leo.lock`. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(deny_unknown_fields)] @@ -107,12 +76,8 @@ impl Lock { Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(Self::default()), Err(err) => return Err(crate::errors::invalid_lock_file(path.display(), err).into()), }; - Self::parse(&path, &contents) - } - - fn parse(path: &Path, contents: &str) -> Result { let mut lock: Self = - serde_json::from_str(contents).map_err(|err| crate::errors::invalid_lock_file(path.display(), err))?; + serde_json::from_str(&contents).map_err(|err| crate::errors::invalid_lock_file(path.display(), err))?; if !matches!(lock.version, 1 | LOCK_VERSION) { return Err(crate::errors::invalid_lock_file(path.display(), "unsupported lock version").into()); } diff --git a/crates/package/src/tests.rs b/crates/package/src/tests.rs index 24035b5e444..c2e5d6ee27c 100644 --- a/crates/package/src/tests.rs +++ b/crates/package/src/tests.rs @@ -139,10 +139,10 @@ fn first_network_fetch_ignores_unverified_cache_and_records_checksum() { assert_eq!(unit.edition, Some(0)); assert_eq!(server.join().expect("fixture must finish").len(), 1); assert_eq!(std::fs::read_to_string(cache).expect("cache must exist"), TRUSTED_TOKEN); - lock.network_pin("token.aleo", leo_ast::NetworkName::TestnetV0, Some(0)) - .expect("the first download must record a checksum") - .verify(TRUSTED_TOKEN) - .expect("the downloaded bytecode must match the new checksum"); + assert_eq!( + serde_json::json!(lock.network_pin("token.aleo", leo_ast::NetworkName::TestnetV0, Some(0))), + network_pin(TRUSTED_TOKEN, 0), + ); std::fs::remove_dir_all(home).expect("test directory must be removed"); }); } @@ -243,15 +243,15 @@ fn explicit_edition_change_records_fresh_checksum_and_preserves_other_networks() lock.carry_over(&old, |_| true); lock.write(&home).expect("updated lock must write"); let lock = Lock::read(&home).expect("updated lock must read"); - lock.network_pin("token.aleo", leo_ast::NetworkName::TestnetV0, Some(3)) - .expect("new edition must survive carry-over") - .verify(&updated) - .expect("new checksum must match"); + assert_eq!( + serde_json::json!(lock.network_pin("token.aleo", leo_ast::NetworkName::TestnetV0, Some(3))), + network_pin(&updated, 3), + ); assert!(lock.network_pin("token.aleo", leo_ast::NetworkName::TestnetV0, Some(2)).is_none()); - lock.network_pin("token.aleo", leo_ast::NetworkName::MainnetV0, Some(2)) - .expect("other network must remain") - .verify(TRUSTED_TOKEN) - .expect("other checksum must remain"); + assert_eq!( + lock.network_pin("token.aleo", leo_ast::NetworkName::MainnetV0, Some(2)), + old.network_pin("token.aleo", leo_ast::NetworkName::MainnetV0, Some(2)), + ); std::fs::remove_dir_all(home).expect("test directory must be removed"); }); } @@ -460,7 +460,7 @@ fn legacy_git_lock_is_readable_and_network_pins_survive_git_updates() { let pin = reloaded .network_pin("token.aleo", leo_ast::NetworkName::TestnetV0, Some(2)) .expect("network pin must survive Git changes"); - pin.verify(TRUSTED_TOKEN).expect("network checksum must remain unchanged"); + assert_eq!(serde_json::json!(pin), network_pin(TRUSTED_TOKEN, 2)); assert!(reloaded.commit_for("token.aleo", "url", "default").is_none()); assert_eq!(reloaded.commit_for("other", "url2", "default"), Some("new")); updated.remove_name("other"); @@ -500,14 +500,14 @@ fn package_automatically_locks_transitive_network_imports() { let names: Vec<_> = package.compilation_units.iter().map(|unit| unit.name.to_string()).collect(); assert_eq!(names, ["token.aleo", "parent.aleo", "consumer.aleo"]); let lock = Lock::read(&consumer).expect("automatic lock must exist"); - lock.network_pin("parent.aleo", leo_ast::NetworkName::TestnetV0, Some(2)) - .expect("parent pin must exist") - .verify(&parent) - .expect("parent checksum must match"); - lock.network_pin("token.aleo", leo_ast::NetworkName::TestnetV0, Some(1)) - .expect("transitive pin must exist") - .verify(TRUSTED_TOKEN) - .expect("transitive checksum must match"); + assert_eq!( + serde_json::json!(lock.network_pin("parent.aleo", leo_ast::NetworkName::TestnetV0, Some(2))), + network_pin(&parent, 2), + ); + assert_eq!( + serde_json::json!(lock.network_pin("token.aleo", leo_ast::NetworkName::TestnetV0, Some(1))), + network_pin(TRUSTED_TOKEN, 1), + ); let original_lock = std::fs::read(consumer.join(LOCK_FILENAME)).expect("lock must exist"); Package::from_directory( &consumer, From 0377967e76526d10a86cbdf3bd7592e7139b210f Mon Sep 17 00:00:00 2001 From: Mohammad Fawaz Date: Wed, 30 Sep 2026 16:35:47 -0400 Subject: [PATCH 06/10] Repurpose leo update for dependency lock refreshes --- README.md | 15 +- crates/leo/src/cli/cli.rs | 21 +- crates/leo/src/cli/commands/update.rs | 96 +++--- crates/leo/src/cli/helpers/updater.rs | 104 +------ crates/leo/src/errors.rs | 19 +- crates/package/README.md | 6 +- crates/package/src/dependency.rs | 4 +- crates/package/src/git.rs | 14 +- crates/package/src/lock.rs | 25 ++ crates/package/src/package.rs | 172 +++++++++-- crates/package/src/tests.rs | 283 +++++++++++++++++- documentation/cli/plugins.md | 2 +- documentation/cli/update.md | 50 ++-- tests/expectations/cli/test_deploy/COMMANDS | 43 ++- tests/expectations/cli/test_deploy/STDOUT | 2 +- .../cli/test_git_dependency/COMMANDS | 48 +++ .../cli/test_git_dependency/STDOUT | 1 + tests/tests/cli/test_deploy/COMMANDS | 43 ++- tests/tests/cli/test_git_dependency/COMMANDS | 48 +++ 19 files changed, 765 insertions(+), 231 deletions(-) diff --git a/README.md b/README.md index 8c2324d036b..57b1bb1298e 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Leo is an imperative, statically-typed programming language built for writing pr * [🦀 Install Rust](#-install-rust) * [📦 Download using Cargo](#-download-using-cargo) * [🐙 Build from Source Code](#-build-from-source-code) - * [🦁 Update from Leo](#-update-from-leo) + * [🦁 Update Leo](#-update-leo) * [🚀 Quick Start](#-quick-start) * [🧰 Troubleshooting](#-troubleshooting) * [📖 Documentation](#-documentation) @@ -96,23 +96,22 @@ cargo install --path crates/leo cargo install --path crates/fmt ``` -### 🦁 Update from Leo +### 🦁 Update Leo -You can update Leo and its plugins to the latest released version using the -following command: +To install the latest released version of Leo and its plugins, run: ```bash -leo update +cargo install leo-lang leo-fmt leo-lsp ``` -Note that if you were using a prerelease version of Leo, this will overwrite -that with the latest released version. +To check the installed version, run: -Now to check the version of leo, in your terminal, run: ```bash leo --version ``` +Use `leo update` to update project dependency locks. This command does not install Leo or its plugins. + ## 🚀 Quick Start Use the Leo CLI to create a new project diff --git a/crates/leo/src/cli/cli.rs b/crates/leo/src/cli/cli.rs index d2f2183bc43..e722a604a10 100644 --- a/crates/leo/src/cli/cli.rs +++ b/crates/leo/src/cli/cli.rs @@ -133,7 +133,7 @@ enum Commands { }, #[clap(about = "List installed leo plugins")] Plugins, - #[clap(about = "Update the Leo CLI")] + #[clap(about = "Update dependencies in leo.lock within the manifest constraints")] Update { #[clap(flatten)] command: LeoUpdate, @@ -335,6 +335,25 @@ mod tests { use serial_test::serial; use std::{env::temp_dir, path::PathBuf}; + #[test] + fn update_accepts_dependency_options_and_rejects_self_update_flags() { + for arguments in [vec!["leo", "update"], vec![ + "leo", + "update", + "token.aleo", + "--dry-run", + "--network", + "testnet", + "--endpoint", + "http://localhost:3030", + ]] { + CLI::try_parse_from(arguments).expect("Dependency update arguments must parse"); + } + for arguments in [vec!["leo", "update", "--list"], vec!["leo", "update", "--name", "v4.4.4"]] { + assert!(CLI::try_parse_from(arguments).is_err(), "Self-update flags must be removed"); + } + } + #[test] fn network_pins_use_the_workspace_root() { use crate::cli::context::Context; diff --git a/crates/leo/src/cli/commands/update.rs b/crates/leo/src/cli/commands/update.rs index 0e62a6824b1..510446ea52b 100644 --- a/crates/leo/src/cli/commands/update.rs +++ b/crates/leo/src/cli/commands/update.rs @@ -15,20 +15,18 @@ // along with the Leo library. If not, see . use super::*; -use crate::cli::helpers::updater::Updater; +use leo_ast::NetworkName; +use leo_package::Package; -/// Update Leo to the latest version +/// Update dependencies within the manifest constraints. #[derive(Debug, Parser)] pub struct LeoUpdate { - /// Lists all available versions of Leo - #[clap(short = 'l', long, help = "List all available releases.")] - list: bool, - /// Update to a specific named release - #[clap(short = 'n', long, help = "An optional release name.")] + #[clap(value_name = "NAME", help = "Update only this dependency; otherwise update all dependencies.")] name: Option, - /// Suppress outputs to terminal - #[clap(short = 'q', long, help = "Suppress download logs.")] - quiet: bool, + #[clap(long, help = "Show dependency updates without changing leo.lock.")] + dry_run: bool, + #[clap(flatten)] + env_override: EnvOptions, } impl Command for LeoUpdate { @@ -43,34 +41,58 @@ impl Command for LeoUpdate { Ok(()) } - fn apply(self, _: Context, _: Self::Input) -> Result - where - Self: Sized, - { - match self.list { - true => match Updater::show_available_releases() { - Ok(output) => tracing::info!("{output}"), - Err(error) => tracing::info!("Failed to list the available versions of Leo\n{error}\n"), - }, - false => { - let show_output = !self.quiet; - let result = Updater::update(show_output, self.name.clone()); - if show_output { - match &result { - Ok(status) => { - if status.uptodate() { - tracing::info!("\nLeo is already on the latest version") - } else if status.updated() { - tracing::info!("\nLeo has updated to version {}", status.version()) - } - } - Err(e) => tracing::info!("\nFailed to update Leo to the latest version\n{e}\n"), - } - } - if result.is_ok() { - Updater::update_bundled_plugins(show_output, self.name.as_deref()); - } + fn apply(self, context: Context, _: Self::Input) -> Result { + if context.package_filter.is_some() { + return Err(crate::errors::custom( + "Dependency updates use the shared workspace lock. Use `leo update NAME` to select a dependency.", + ) + .into()); + } + let network = get_network(&self.env_override.network).unwrap_or_else(|_| { + tracing::warn!("No network specified, defaulting to 'testnet'."); + NetworkName::TestnetV0 + }); + let endpoint = get_endpoint(&self.env_override.endpoint).unwrap_or_else(|_| DEFAULT_ENDPOINT.to_string()); + let (old, updated) = Package::update_dependencies( + &context.dir()?, + &context.home()?, + self.name.as_deref(), + self.dry_run, + network, + &endpoint, + self.env_override.network_retries, + )?; + + let action = if self.dry_run { "Would update" } else { "Updated" }; + let mut changed = false; + for pin in updated.network_entries() { + let previous = old.network_entries().iter().find(|old| old.name == pin.name && old.network == pin.network); + if previous == Some(pin) { + continue; + } + changed = true; + let previous = previous.map(|pin| pin.edition.to_string()).unwrap_or_else(|| "unlocked".to_string()); + tracing::info!("{action} {} ({}) edition {previous} -> {}", pin.name, pin.network, pin.edition); + } + for pin in updated.git_entries() { + let previous = old.commit_for(&pin.name, &pin.git, &pin.reference); + if previous == Some(pin.commit.as_str()) { + continue; } + changed = true; + tracing::info!( + "{action} {} ({}) {:.12} -> {:.12}", + pin.name, + pin.reference, + previous.unwrap_or("unlocked"), + pin.commit + ); + } + if !changed { + tracing::info!("No dependency updates."); + } + if self.dry_run { + tracing::info!("Dry run: leo.lock was not changed."); } Ok(()) } diff --git a/crates/leo/src/cli/helpers/updater.rs b/crates/leo/src/cli/helpers/updater.rs index 294ab3d8661..a94dfde9e5c 100644 --- a/crates/leo/src/cli/helpers/updater.rs +++ b/crates/leo/src/cli/helpers/updater.rs @@ -19,9 +19,8 @@ use leo_errors::{Backtraced, Result}; use aleo_std; use colored::Colorize; -use self_update::{Status, backends::github, get_target, version::bump_is_greater}; +use self_update::{backends::github, version::bump_is_greater}; use std::{ - fmt::Write as _, fs, path::{Path, PathBuf}, time::{Duration, SystemTime, UNIX_EPOCH}, @@ -29,7 +28,6 @@ use std::{ pub struct Updater; -// TODO Add logic for users to easily select release versions. impl Updater { const LEO_BIN_NAME: &'static str = "leo"; const LEO_CACHE_LAST_CHECK_FILE: &'static str = "leo_cache_last_update_check"; @@ -39,100 +37,6 @@ impl Updater { // 24 hours const LEO_UPDATE_CHECK_INTERVAL: Duration = Duration::from_secs(24 * 60 * 60); - /// Show all available releases for `leo`. - pub fn show_available_releases() -> Result { - let releases = github::ReleaseList::configure() - .repo_owner(Self::LEO_REPO_OWNER) - .repo_name(Self::LEO_REPO_NAME) - .with_target(get_target()) - .build() - .map_err(crate::errors::self_update_error)? - .fetch() - .map_err(crate::errors::could_not_fetch_versions)?; - - let mut output = format!( - "\nList of available versions for: {}.\nUse the quoted name to select specific releases.\n\n", - get_target() - ); - for release in releases { - let _ = writeln!(output, " * {} | '{}'", release.version, release.name); - } - - Ok(output) - } - - /// Update `leo`. If a version is provided, then `leo` is updated to the specific version - /// otherwise the update defaults to the latest version. - pub fn update(show_output: bool, version: Option) -> Result { - let mut update = github::Update::configure(); - // Set the defaults. - update - .repo_owner(Self::LEO_REPO_OWNER) - .repo_name(Self::LEO_REPO_NAME) - .bin_name(Self::LEO_BIN_NAME) - .current_version(env!("CARGO_PKG_VERSION")) - .show_download_progress(show_output) - .no_confirm(true) - .show_output(show_output); - // Add the version if provided. - if let Some(version) = version { - update.target_version_tag(&version); - } - let status = update - .build() - .map_err(crate::errors::self_update_build_error)? - .update() - .map_err(crate::errors::self_update_error)?; - - Ok(status) - } - - /// Best-effort update of bundled plugin binaries (currently just `leo-fmt`). - /// - /// Downloads the release archive a second time and extracts the plugin binary - /// into the same directory as the current `leo` executable. Prints a warning - /// on failure rather than aborting. - pub fn update_bundled_plugins(show_output: bool, version: Option<&str>) { - const BUNDLED_PLUGINS: &[&str] = &["leo-fmt"]; - - let install_dir = match std::env::current_exe().ok().and_then(|p| p.parent().map(Path::to_path_buf)) { - Some(dir) => dir, - None => { - tracing::warn!("Could not determine leo install directory; skipping plugin update"); - return; - } - }; - - for plugin in BUNDLED_PLUGINS { - if show_output { - tracing::info!("Updating bundled plugin '{plugin}'..."); - } - let mut update = github::Update::configure(); - update - .repo_owner(Self::LEO_REPO_OWNER) - .repo_name(Self::LEO_REPO_NAME) - .bin_name(plugin) - .bin_install_path(&install_dir) - .current_version(env!("CARGO_PKG_VERSION")) - .show_download_progress(show_output) - .no_confirm(true) - .show_output(show_output); - if let Some(ver) = version { - update.target_version_tag(ver); - } - match update.build().and_then(|u| u.update()) { - Ok(_) => { - if show_output { - tracing::info!("Successfully updated '{plugin}'"); - } - } - Err(e) => { - tracing::warn!("Failed to update bundled plugin '{plugin}': {e}"); - } - } - } - } - /// Check if there is an available update for `leo` and return the newest release. pub fn update_available() -> Result { let updater = github::Update::configure() @@ -167,9 +71,9 @@ impl Updater { if let Some(latest_version) = Self::read_latest_version()? { let colorized_message = format!( "\n🟢 {} {} {}", - "A new version is available! Run".bold().green(), - "`leo update`".bold().white(), - format!("to update to v{latest_version}.").bold().green() + format!("Leo v{latest_version} is available.").bold().green(), + "For installation instructions, see".bold().green(), + "https://github.com/ProvableHQ/leo#-build-guide".bold().white() ); Ok(Some(colorized_message)) } else { diff --git a/crates/leo/src/errors.rs b/crates/leo/src/errors.rs index a342e4096cb..77d585cd470 100644 --- a/crates/leo/src/errors.rs +++ b/crates/leo/src/errors.rs @@ -35,29 +35,18 @@ pub(crate) fn cli_runtime_error(error: impl Display) -> Backtraced { Backtraced::error(CODE_PREFIX, CODE_MASK + 2, format!("CLI runtime error: {error}")) } -pub(crate) fn could_not_fetch_versions(error: impl ErrorArg) -> Backtraced { - Backtraced::error(CODE_PREFIX, CODE_MASK + 3, format!("could not fetch Leo release versions: {error}")) - .with_help("Check your internet connection and retry. If the problem persists, download a release manually from https://github.com/ProvableHQ/leo/releases.") -} - pub(crate) fn self_update_error(error: impl ErrorArg) -> Backtraced { - Backtraced::error(CODE_PREFIX, CODE_MASK + 5, format!("self-update failed: {error}")).with_help( - "Retry the update, or download the latest release manually from https://github.com/ProvableHQ/leo/releases.", - ) -} - -pub(crate) fn self_update_build_error(error: impl ErrorArg) -> Backtraced { - Backtraced::error(CODE_PREFIX, CODE_MASK + 6, format!("self-update failed to build the updater: {error}")) - .with_help("Retry the update, or download the latest release manually from https://github.com/ProvableHQ/leo/releases.") + Backtraced::error(CODE_PREFIX, CODE_MASK + 5, format!("Leo release check failed: {error}")) + .with_help("Check the available releases at https://github.com/ProvableHQ/leo/releases.") } pub(crate) fn old_release_version(current: impl Display, latest: impl Display) -> Backtraced { Backtraced::error( CODE_PREFIX, CODE_MASK + 7, - format!("Leo `{current}` is older than the latest release `{latest}`"), + format!("No newer Leo release is available (installed: `{current}`, latest: `{latest}`)"), ) - .with_help("Run `leo update` to upgrade to the latest version.") + .with_help("For installation instructions, see https://github.com/ProvableHQ/leo#-build-guide.") } pub(crate) fn failed_to_load_instructions(error: impl Display) -> Backtraced { diff --git a/crates/package/README.md b/crates/package/README.md index 535f6ff1a95..e8841531a80 100644 --- a/crates/package/README.md +++ b/crates/package/README.md @@ -12,7 +12,11 @@ The first download trusts the configured endpoint. Leo checks the program ID and Later builds use the locked edition. Leo checks cached and downloaded bytecode against its checksum. A checksum mismatch stops the operation without replacing the lock or cache. `--no-cache` downloads the locked edition again; it does not select a new edition or replace its checksum. -To update a dependency, use `leo add` with the required `--edition`. A different edition is fetched from the endpoint and gets a new checksum. Re-adding a dependency without an edition selects the latest edition. If the edition is unchanged, the existing checksum still applies. +Use `leo update` to refresh dependencies, or `leo update NAME` to refresh one dependency. Omitted network editions can advance to the latest edition. Explicit manifest editions, Git tags, and Git revisions stay fixed. A named update preserves unrelated pins and existing transitive pins where possible. Dependencies from the same Git repository and branch update together. + +Use `leo update --dry-run` to show proposed changes without writing the lock, manifests, or build output. It can download data to the cache. In a workspace, updates include every member and development dependency, even when the command runs from one member. A fixed edition in any member constrains the update. If the selected network edition is unchanged, its existing checksum still applies. + +Normal builds reuse locked network editions and Git commits. A missing Git checkout is restored at the locked commit. To change an explicit edition, use `leo add` with the required `--edition`. Dependency updates do not change manifest constraints, and `leo update` does not update the Leo executable. Leo reads and writes the lock at the workspace root, or beside `program.json` for a standalone project. It preserves network entries for other workspace members, development dependencies, and networks. A build writes the lock only after all dependencies are resolved and the dependency graph is valid. The file replacement is atomic. diff --git a/crates/package/src/dependency.rs b/crates/package/src/dependency.rs index 1bcb4a0f098..da8f5198be0 100644 --- a/crates/package/src/dependency.rs +++ b/crates/package/src/dependency.rs @@ -61,7 +61,7 @@ impl GitSource { /// The git reference a dependency tracks, derived from its `branch`/`tag`/`rev` fields. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub enum GitReference { - /// Tip of a named branch. Mutable: re-resolved online each build. + /// Tip of a named branch. Mutable: refreshed by `leo update`. Branch(String), /// A tag. Immutable: reused from cache once fetched. Tag(String), @@ -72,7 +72,7 @@ pub enum GitReference { } impl GitReference { - /// Whether the reference tracks a moving target (a branch tip) and must be re-resolved online. + /// Whether the reference tracks a branch tip that `leo update` can refresh. pub fn is_mutable(&self) -> bool { matches!(self, GitReference::Branch(_) | GitReference::DefaultBranch) } diff --git a/crates/package/src/git.rs b/crates/package/src/git.rs index 3c4a5cef7af..8f80b64b43a 100644 --- a/crates/package/src/git.rs +++ b/crates/package/src/git.rs @@ -60,8 +60,8 @@ pub(crate) fn checkout_dir(home: &Path, url: &str, commit: &str) -> PathBuf { /// Resolve a git dependency to a `(checkout_directory, commit_hash)`. /// -/// A `locked_commit` from `leo.lock` is reused when its checkout exists and the reference is -/// immutable (tag/rev) or `offline`; a mutable reference re-resolves against the remote when online. +/// A `locked_commit` from `leo.lock` is reused. Without a cached checkout, the repository is +/// fetched and that same commit is restored. A missing pin resolves the requested reference. /// `offline` forbids network access, so it then succeeds only from an existing checkout. pub fn resolve( home: &Path, @@ -71,11 +71,7 @@ pub fn resolve( locked_commit: Option<&str>, offline: bool, ) -> Result<(PathBuf, String)> { - // Fast path: reuse a cached locked commit, but only for an immutable reference (or when - // offline) — a mutable branch tip must be re-resolved online. - if let Some(commit) = locked_commit - && (offline || !reference.is_mutable()) - { + if let Some(commit) = locked_commit { let dir = checkout_dir(home, url, commit); if dir.is_dir() { return Ok((dir, commit.to_string())); @@ -125,8 +121,8 @@ fn clone_resolve_checkout( let repo = checkout.persist(); let revspec = match (locked_commit, reference) { - // Immutable ref + locked commit: use the pin so a moved tag can't change the build. - (Some(commit), reference) if !reference.is_mutable() => commit.to_string(), + // Restore the locked commit even when its branch or tag has moved. + (Some(commit), _) => commit.to_string(), (_, GitReference::DefaultBranch) => "HEAD".to_string(), (_, GitReference::Branch(branch)) => format!("origin/{branch}"), (_, GitReference::Tag(tag)) => format!("refs/tags/{tag}"), diff --git a/crates/package/src/lock.rs b/crates/package/src/lock.rs index 0ad067e25eb..9f67b7a7269 100644 --- a/crates/package/src/lock.rs +++ b/crates/package/src/lock.rs @@ -112,6 +112,31 @@ impl Lock { self.network.push(entry); } + /// Read the recorded Git dependencies. + pub fn git_entries(&self) -> &[GitLockEntry] { + &self.git + } + + /// Read the recorded network dependencies. + pub fn network_entries(&self) -> &[NetworkLockEntry] { + &self.network + } + + /// Release selected branch pins, including other dependencies from the same source. + pub(crate) fn unlock_git(&mut self, name: Option<&str>) { + let sources: IndexSet<_> = self + .git + .iter() + .filter(|entry| { + (entry.reference == "default" || entry.reference.starts_with("branch=")) + && name.is_none_or(|name| crate::bare_unit_name(&entry.name) == crate::bare_unit_name(name)) + }) + .map(|entry| (entry.git.clone(), entry.reference.clone())) + .collect(); + self.git + .retain(|entry| !sources.iter().any(|(git, reference)| &entry.git == git && &entry.reference == reference)); + } + /// The pinned commit for `(name, git, reference)`, or `None` (forcing re-resolution) on any mismatch. pub fn commit_for(&self, name: &str, git: &str, reference: &str) -> Option<&str> { self.git.iter().find(|e| e.name == name && e.git == git && e.reference == reference).map(|e| e.commit.as_str()) diff --git a/crates/package/src/package.rs b/crates/package/src/package.rs index cb9cdd2aff3..6a84d9113f7 100644 --- a/crates/package/src/package.rs +++ b/crates/package/src/package.rs @@ -254,6 +254,8 @@ impl Package { network, endpoint, network_retries, + None, + None, ) } @@ -309,6 +311,8 @@ impl Package { network, endpoint, network_retries, + None, + None, ) } @@ -336,9 +340,102 @@ impl Package { network, endpoint, network_retries, + None, + None, ) } + /// Update project or workspace dependencies without changing manifests or build artifacts. + #[allow(clippy::too_many_arguments)] + pub fn update_dependencies( + path: &Path, + home_path: &Path, + name: Option<&str>, + dry_run: bool, + network: NetworkName, + endpoint: &str, + network_retries: u32, + ) -> Result<(Lock, Lock)> { + let workspace = Workspace::discover(path)?; + let root = workspace.as_ref().map_or(path, |workspace| workspace.root_directory.as_path()); + let paths = + workspace.as_ref().map_or_else(|| vec![path.to_path_buf()], |workspace| workspace.member_paths.clone()); + let mut constraints = IndexMap::new(); + for path in &paths { + let manifest = Manifest::read_from_file(path.join(MANIFEST_FILENAME))?; + for dependency in collect_declared_deps(path, &manifest, true)?.values() { + if dependency.location == Location::Network + && let Some(edition) = dependency.edition + { + let program = canonicalize_program_name(&dependency.name); + if let Some(previous) = constraints.insert(program.clone(), edition) + && previous != edition + { + return Err(anyhow!( + "Conflicting editions for network dependency `{program}`: {previous} and {edition}." + ) + .into()); + } + } + } + } + std::fs::create_dir_all(home_path).map_err(|error| crate::errors::failed_path(home_path.display(), error))?; + let old_lock = Lock::read(root)?; + let mut resolution_lock = old_lock.clone(); + resolution_lock.unlock_git(name); + let mut new_lock = Lock::default(); + new_lock.carry_over(&old_lock, |_| false); + let mut resolved = IndexMap::new(); + let mut found = name.is_none(); + for path in paths { + let package = Self::from_directory_impl( + &path, + home_path, + true, + true, + false, + false, + false, + Some(network), + Some(endpoint), + network_retries, + Some((&resolution_lock, &mut new_lock)), + Some((name, &constraints)), + )?; + for unit in &package.compilation_units { + if let Some(name) = name { + found |= unit.dependencies.iter().any(|dependency| { + matches!(dependency.location, Location::Network | Location::Git) + && bare_unit_name(&dependency.name) == bare_unit_name(name) + }); + } + if !unit.is_local + && let Some(edition) = unit.edition + && let Some(previous) = resolved.insert(unit.name, edition) + && previous != edition + { + return Err(anyhow!( + "Conflicting editions for network dependency `{}`: {previous} and {edition}.", + unit.name + ) + .into()); + } + } + } + if !found { + return Err(anyhow!( + "No network or Git dependency named `{}` exists in this project or workspace.", + name.unwrap_or_default() + ) + .into()); + } + new_lock.carry_over(&old_lock, |_| true); + if !dry_run { + new_lock.write(root)?; + } + Ok((old_lock, new_lock)) + } + pub fn test_files(&self) -> impl Iterator { let path = self.tests_directory(); // This allocation isn't ideal but it's not performance critical and @@ -446,6 +543,7 @@ impl Package { &old_lock, &mut new_lock, false, + None, )?; let compilation_units = digraph @@ -484,6 +582,8 @@ impl Package { network: Option, endpoint: Option<&str>, network_retries: u32, + resolution: Option<(&Lock, &mut Lock)>, + update: Option<(Option<&str>, &IndexMap)>, ) -> Result { let map_err = |path: &Path, err| { crate::errors::util_file_io_error(format_args!("Trying to find path at {}", path.display()), err) @@ -512,9 +612,11 @@ impl Package { // The lock lives at the workspace root, else beside this package's `program.json`. let lock_dir = workspace_root.as_deref().unwrap_or(&path).to_path_buf(); // Reuse network pins and record Git resolutions for this build. - let old_lock = Lock::read(&lock_dir)?; - let mut new_lock = Lock::default(); - new_lock.carry_over(&old_lock, |_| false); + let persist = resolution.is_none(); + let file_lock = if persist { Lock::read(&lock_dir)? } else { Lock::default() }; + let mut file_resolution = Lock::default(); + file_resolution.carry_over(&file_lock, |_| false); + let (old_lock, new_lock) = resolution.unwrap_or((&file_lock, &mut file_resolution)); let first_dependency = Dependency { name: manifest.program.clone(), @@ -568,17 +670,18 @@ impl Package { None, network_retries, &declared_deps, - &old_lock, - &mut new_lock, + old_lock, + new_lock, offline, + update, )?; } // Workspace: carry all entries since the lock is shared. Standalone: carry only dev-git // names (a plain build skips dev deps, so their pins may legitimately be unresolved). - if workspace_root.is_some() { - new_lock.carry_over(&old_lock, |_| true); - } else { + if persist && workspace_root.is_some() { + new_lock.carry_over(old_lock, |_| true); + } else if persist { let dev_git_names: Vec<&str> = if with_tests { Vec::new() } else { @@ -590,13 +693,15 @@ impl Package { .map(|dep| dep.name.as_str()) .collect() }; - new_lock.carry_over(&old_lock, |entry| dev_git_names.contains(&entry.name.as_str())); + new_lock.carry_over(old_lock, |entry| dev_git_names.contains(&entry.name.as_str())); } let ordered_dependency_symbols = digraph.post_order().map_err(|_| crate::errors::circular_dependency_error())?; // Commit the complete lock only after the dependency graph is valid. - new_lock.write(&lock_dir)?; + if persist { + new_lock.write(&lock_dir)?; + } ( ordered_dependency_symbols.into_iter().map(|symbol| map.swap_remove(&symbol).unwrap().1).collect(), @@ -626,6 +731,7 @@ impl Package { old_lock: &Lock, new_lock: &mut Lock, offline: bool, + update: Option<(Option<&str>, &IndexMap)>, ) -> Result<()> { let mut new = new; if new.location == Location::Network @@ -669,7 +775,9 @@ impl Package { assert_eq!(new.name, existing_dep.name); if new.location != existing_dep.location || new.path != existing_dep.path - || new.edition != existing_dep.edition + || (new.edition != existing_dep.edition + && !(new.location == Location::Network + && new.edition.is_none_or(|edition| occupied.get().1.edition == Some(edition)))) || new.git != existing_dep.git { return Err(crate::errors::conflicting_dependency(existing_dep, new).into()); @@ -699,13 +807,29 @@ impl Package { let Some(network) = network else { return Err(anyhow!("A network must be provided to fetch network dependencies.").into()); }; + let selected = update.is_some_and(|(name, _)| { + name.is_none_or(|name| bare_unit_name(&new.name) == bare_unit_name(name)) + }); + let edition = new + .edition + .or_else(|| declared_deps.get(&name_symbol).and_then(|dependency| dependency.edition)) + .or_else(|| { + update.and_then(|(_, constraints)| { + constraints.get(&canonicalize_program_name(&new.name)).copied() + }) + }); + let edition = if selected && edition.is_none() && bare_unit_name(&new.name) != "credits" { + Some(crate::fetch_latest_edition(&new.name, endpoint, network, network_retries)?) + } else { + edition + }; CompilationUnit::fetch( name_symbol, - new.edition, + edition, home_path, network, endpoint, - no_cache, + no_cache || selected, network_retries, new_lock, )? @@ -784,6 +908,7 @@ impl Package { old_lock, new_lock, offline, + update, )?; } @@ -914,10 +1039,23 @@ fn collect_declared_deps_recursive( // Only recurse into newly discovered dependencies to avoid infinite // recursion on circular manifests (cycles are caught later by // `DiGraph::post_order`). - let Entry::Vacant(e) = declared.entry(sym) else { - continue; - }; - e.insert(dep.clone()); + match declared.entry(sym) { + Entry::Occupied(mut entry) => { + let existing = entry.get_mut(); + if existing.location == Location::Network && dep.location == Location::Network { + if let (Some(previous), Some(edition)) = (existing.edition, dep.edition) + && previous != edition + { + return Err(crate::errors::conflicting_dependency(existing, dep).into()); + } + existing.edition = existing.edition.or(dep.edition); + } + continue; + } + Entry::Vacant(entry) => { + entry.insert(dep.clone()); + } + } if dep.location == Location::Local && let Some(path) = &dep.path { diff --git a/crates/package/src/tests.rs b/crates/package/src/tests.rs index c2e5d6ee27c..52a68deb32f 100644 --- a/crates/package/src/tests.rs +++ b/crates/package/src/tests.rs @@ -779,6 +779,268 @@ fn lock_write_failure_preserves_existing_lock() { std::fs::remove_dir_all(dir).expect("The test directory must be removed."); } +#[test] +fn dependency_update_creates_lock_and_dry_run_preserves_project_files() { + leo_span::create_session_if_not_set_then(|_| { + let base = unique_dir("update-first-lock"); + let home = base.join("home"); + let consumer = base.join("consumer"); + std::fs::create_dir_all(&home).expect("home must exist"); + write_consumer(&consumer, r#"{"name":"token.aleo","location":"network","edition":0}"#); + let manifest = std::fs::read(consumer.join(MANIFEST_FILENAME)).expect("manifest must exist"); + for dry_run in [true, false] { + let (endpoint, server) = network_response(&[("/testnet/program/token.aleo/0", "200 OK", TRUSTED_TOKEN)]); + let (old, new) = Package::update_dependencies( + &consumer, + &home, + None, + dry_run, + leo_ast::NetworkName::TestnetV0, + &endpoint, + 0, + ) + .expect("an update must resolve a missing lock"); + assert!(old.is_empty()); + assert!(new.network_pin("token.aleo", leo_ast::NetworkName::TestnetV0, Some(0)).is_some()); + assert_eq!(consumer.join(LOCK_FILENAME).exists(), !dry_run); + assert_eq!(server.join().expect("fixture must finish").len(), 1); + assert_eq!(std::fs::read(consumer.join(MANIFEST_FILENAME)).expect("manifest must remain"), manifest); + assert!(!consumer.join("build").exists()); + } + std::fs::remove_dir_all(base).expect("test directory must be removed"); + }); +} + +#[test] +fn dependency_update_is_selective_and_keeps_transitive_pins() { + leo_span::create_session_if_not_set_then(|_| { + let base = unique_dir("update-selected"); + let home = base.join("home"); + let consumer = base.join("consumer"); + let parent = format!("import token.aleo;\n{}", TRUSTED_TOKEN.replace("token.aleo", "parent.aleo")); + let other = TRUSTED_TOKEN.replace("token.aleo", "other.aleo"); + write_consumer( + &consumer, + r#"{"name":"parent.aleo","location":"network"},{"name":"other.aleo","location":"network"}"#, + ); + write_network_lock(&consumer, &[ + network_pin(&parent, 0), + network_pin(TRUSTED_TOKEN, 0), + network_pin(&other, 0), + ]); + write_file(&home.join("registry/testnet/token/0/token.aleo"), TRUSTED_TOKEN); + write_file(&home.join("registry/testnet/other/0/other.aleo"), &other); + let original = std::fs::read(consumer.join(LOCK_FILENAME)).expect("lock must exist"); + for dry_run in [true, false] { + let (endpoint, server) = network_response(&[ + ("/testnet/program/parent.aleo/latest_edition", "200 OK", "1"), + ("/testnet/program/parent.aleo/1", "200 OK", &parent), + ]); + let (_, updated) = Package::update_dependencies( + &consumer, + &home, + Some("parent"), + dry_run, + leo_ast::NetworkName::TestnetV0, + &endpoint, + 0, + ) + .expect("selected update must leave unrelated dependencies locked"); + assert_eq!(server.join().expect("fixture must finish").len(), 2); + assert!(updated.network_pin("parent.aleo", leo_ast::NetworkName::TestnetV0, Some(1)).is_some()); + for name in ["token.aleo", "other.aleo"] { + assert!(updated.network_pin(name, leo_ast::NetworkName::TestnetV0, Some(0)).is_some()); + } + if dry_run { + assert_eq!(std::fs::read(consumer.join(LOCK_FILENAME)).expect("lock must remain"), original); + } + } + let original = std::fs::read(consumer.join(LOCK_FILENAME)).expect("lock must exist"); + let changed = parent.replace("private", "public"); + let (endpoint, server) = network_response(&[ + ("/testnet/program/parent.aleo/latest_edition", "200 OK", "1"), + ("/testnet/program/parent.aleo/1", "200 OK", &changed), + ]); + let error = Package::update_dependencies( + &consumer, + &home, + Some("parent.aleo"), + false, + leo_ast::NetworkName::TestnetV0, + &endpoint, + 0, + ) + .expect_err("same-edition updates must not replace the locked checksum"); + assert!(error.to_string().contains("checksum"), "{error}"); + assert_eq!(server.join().expect("fixture must finish").len(), 2); + assert_eq!(std::fs::read(consumer.join(LOCK_FILENAME)).expect("lock must remain"), original); + std::fs::remove_dir_all(base).expect("test directory must be removed"); + }); +} + +#[test] +fn dependency_update_respects_workspace_development_edition_constraints() { + leo_span::create_session_if_not_set_then(|_| { + let base = unique_dir("update-workspace-constraints"); + let home = base.join("home"); + let workspace = base.join("workspace"); + let flexible = workspace.join("flexible"); + let fixed = workspace.join("fixed"); + std::fs::create_dir_all(&home).expect("home must exist"); + write_file(&workspace.join(WORKSPACE_MANIFEST_FILENAME), r#"{"members":["flexible","fixed"]}"#); + write_program(&flexible, "flexible.aleo", r#"[{"name":"token.aleo","location":"network"}]"#); + write_program(&fixed, "fixed.aleo", "null"); + let mut manifest: serde_json::Value = + serde_json::from_str(&std::fs::read_to_string(fixed.join(MANIFEST_FILENAME)).expect("manifest must exist")) + .expect("manifest must parse"); + manifest["dev_dependencies"] = serde_json::json!([{"name":"token.aleo","location":"network","edition":2}]); + write_file(&fixed.join(MANIFEST_FILENAME), &manifest.to_string()); + let mut flexible_manifest: serde_json::Value = serde_json::from_str( + &std::fs::read_to_string(flexible.join(MANIFEST_FILENAME)).expect("manifest must exist"), + ) + .expect("manifest must parse"); + flexible_manifest["dev_dependencies"] = manifest["dev_dependencies"].clone(); + write_file(&flexible.join(MANIFEST_FILENAME), &flexible_manifest.to_string()); + write_network_lock(&workspace, &[network_pin(TRUSTED_TOKEN, 2)]); + let (endpoint, server) = network_response(&[("/testnet/program/token.aleo/2", "200 OK", TRUSTED_TOKEN)]); + let (_, updated) = + Package::update_dependencies(&flexible, &home, None, false, leo_ast::NetworkName::TestnetV0, &endpoint, 0) + .expect("workspace constraints must apply before flexible dependencies select latest editions"); + assert_eq!(server.join().expect("fixture must finish").len(), 2); + assert!(updated.network_pin("token.aleo", leo_ast::NetworkName::TestnetV0, Some(2)).is_some()); + Package::from_directory_with_tests( + &flexible, + &home, + false, + false, + false, + Some(leo_ast::NetworkName::TestnetV0), + Some("http://127.0.0.1:1"), + 0, + ) + .expect("a normal test build must reuse compatible fixed and flexible declarations"); + assert!(!flexible.join(LOCK_FILENAME).exists()); + assert!(!fixed.join(LOCK_FILENAME).exists()); + assert!(!workspace.join("build").exists()); + std::fs::remove_dir_all(base).expect("test directory must be removed"); + }); +} + +#[test] +fn dependency_update_rejects_unknown_local_and_failed_graph_without_writing() { + leo_span::create_session_if_not_set_then(|_| { + let base = unique_dir("update-failure"); + let home = base.join("home"); + let consumer = base.join("consumer"); + let local = base.join("local"); + write_library(&local, "local", "null"); + write_consumer(&consumer, r#"{"name":"local","location":"local","path":"../local"}"#); + let original = r#"{"version":1,"git":[]}"#; + write_file(&consumer.join(LOCK_FILENAME), original); + for name in ["unknown", "local"] { + let error = Package::update_dependencies( + &consumer, + &home, + Some(name), + false, + leo_ast::NetworkName::TestnetV0, + "http://127.0.0.1:1", + 0, + ) + .expect_err("only a present network or Git dependency can be selected"); + assert!(error.to_string().contains("No network or Git dependency"), "{error}"); + assert_eq!(std::fs::read_to_string(consumer.join(LOCK_FILENAME)).expect("lock must remain"), original); + } + write_consumer(&consumer, r#"{"name":"parent.aleo","location":"network","edition":0}"#); + let parent = format!("import token.aleo;\n{}", TRUSTED_TOKEN.replace("token.aleo", "parent.aleo")); + let (endpoint, server) = network_response(&[ + ("/testnet/program/parent.aleo/0", "200 OK", &parent), + ("/testnet/program/token.aleo/latest_edition", "404 Not Found", "missing dependency"), + ]); + assert!( + Package::update_dependencies(&consumer, &home, None, false, leo_ast::NetworkName::TestnetV0, &endpoint, 0,) + .is_err() + ); + assert_eq!(server.join().expect("fixture must finish").len(), 2); + assert_eq!(std::fs::read_to_string(consumer.join(LOCK_FILENAME)).expect("lock must remain"), original); + assert!(!consumer.join("build").exists()); + std::fs::remove_dir_all(base).expect("test directory must be removed"); + }); +} + +#[test] +fn dependency_update_refreshes_git_branches_but_keeps_tags_and_revisions() { + if !git_available() { + return; + } + leo_span::create_session_if_not_set_then(|_| { + let base = unique_dir("update-git"); + let home = base.join("home"); + let source = base.join("source"); + let consumer = base.join("consumer"); + std::fs::create_dir_all(&home).expect("home must exist"); + for name in ["floating", "tagged", "pinned", "other"] { + write_library(&source.join(name), name, "null"); + } + init_repo(&source, None); + let first = run_git(&source, &["rev-parse", "HEAD"]); + run_git(&source, &["tag", "stable"]); + run_git(&source, &["branch", "other"]); + let url = file_url(&source); + write_consumer( + &consumer, + &format!( + r#"{{"name":"floating","location":"git","git":{{"url":"{url}"}}}},{{"name":"tagged","location":"git","git":{{"url":"{url}","tag":"stable"}}}},{{"name":"pinned","location":"git","git":{{"url":"{url}","rev":"{first}"}}}},{{"name":"other","location":"git","git":{{"url":"{url}","branch":"other"}}}}"# + ), + ); + Package::from_directory(&consumer, &home, false, false, false, None, None, 0) + .expect("initial graph must resolve"); + let original = std::fs::read(consumer.join(LOCK_FILENAME)).expect("lock must exist"); + write_file(&source.join("floating/src/lib.leo"), "// updated\n"); + run_git(&source, &["commit", "-qam", "second"]); + let second = run_git(&source, &["rev-parse", "HEAD"]); + run_git(&source, &["tag", "-f", "stable"]); + run_git(&source, &["branch", "-f", "other"]); + Package::from_directory(&consumer, &home, false, false, false, None, None, 0) + .expect("normal build must retain pins"); + assert_eq!(std::fs::read(consumer.join(LOCK_FILENAME)).expect("lock must remain"), original); + for dry_run in [true, false] { + let (_, updated) = Package::update_dependencies( + &consumer, + &home, + Some("floating"), + dry_run, + leo_ast::NetworkName::TestnetV0, + "http://127.0.0.1:1", + 0, + ) + .expect("explicit update must advance only the selected branch"); + assert_eq!(updated.commit_for("floating", &url, "default"), Some(second.as_str())); + assert_eq!(updated.commit_for("tagged", &url, "tag=stable"), Some(first.as_str())); + assert_eq!(updated.commit_for("pinned", &url, &format!("rev={first}")), Some(first.as_str())); + assert_eq!(updated.commit_for("other", &url, "branch=other"), Some(first.as_str())); + if dry_run { + assert_eq!(std::fs::read(consumer.join(LOCK_FILENAME)).expect("lock must remain"), original); + } + } + let (_, updated) = Package::update_dependencies( + &consumer, + &home, + None, + false, + leo_ast::NetworkName::TestnetV0, + "http://127.0.0.1:1", + 0, + ) + .expect("update all must refresh other mutable branches"); + assert_eq!(updated.commit_for("other", &url, "branch=other"), Some(second.as_str())); + assert_eq!(updated.commit_for("tagged", &url, "tag=stable"), Some(first.as_str())); + assert_eq!(updated.commit_for("pinned", &url, &format!("rev={first}")), Some(first.as_str())); + assert!(!consumer.join("build").exists()); + std::fs::remove_dir_all(base).expect("test directory must be removed"); + }); +} + // Reference resolution (`crate::git::resolve`). #[test] @@ -842,7 +1104,7 @@ fn locked_commit_is_reused_without_network() { } #[test] -fn mutable_reference_re_resolves_online_but_reuses_locked_offline() { +fn mutable_reference_reuses_lock_until_explicit_update() { if !git_available() { eprintln!("skipping: `git` CLI not available"); return; @@ -862,10 +1124,16 @@ fn mutable_reference_re_resolves_online_but_reuses_locked_offline() { let c3 = run_git(&src, &["rev-parse", "HEAD"]); assert_ne!(c3, c2); - // Online, the locked commit is ignored for a mutable reference: it re-resolves to the tip. - let (dir, refreshed) = resolve(&home, "dep", &url, &GitReference::DefaultBranch, Some(&c2), false).unwrap(); + // Online builds keep the locked commit even after the branch advances. + let (dir, reused) = resolve(&home, "dep", &url, &GitReference::DefaultBranch, Some(&c2), false).unwrap(); + assert_eq!(reused, c2); + assert_eq!(std::fs::read_to_string(dir.join("a.txt")).unwrap(), "two"); + std::fs::remove_dir_all(home.join("git")).unwrap(); + let (dir, restored) = resolve(&home, "dep", &url, &GitReference::DefaultBranch, Some(&c2), false).unwrap(); + assert_eq!(restored, c2, "a missing checkout must not move the locked revision"); + assert_eq!(std::fs::read_to_string(dir.join("a.txt")).unwrap(), "two"); + let (_, refreshed) = resolve(&home, "dep", &url, &GitReference::DefaultBranch, None, false).unwrap(); assert_eq!(refreshed, c3); - assert_eq!(std::fs::read_to_string(dir.join("a.txt")).unwrap(), "three"); // Offline, the locked commit is reused even for a mutable reference (no network access). let (dir, offline) = resolve(&home, "dep", &url, &GitReference::DefaultBranch, Some(&c2), true).unwrap(); @@ -876,7 +1144,7 @@ fn mutable_reference_re_resolves_online_but_reuses_locked_offline() { } #[test] -fn branch_reference_re_resolves_to_new_tip_online() { +fn branch_reference_refreshes_only_without_a_lock() { if !git_available() { eprintln!("skipping: `git` CLI not available"); return; @@ -896,9 +1164,10 @@ fn branch_reference_re_resolves_to_new_tip_online() { let advanced = run_git(&src, &["rev-parse", "HEAD"]); assert_ne!(advanced, first); - // Online, a mutable branch reference ignores the lock and re-resolves to the new tip. - let (dir, refreshed) = + let (_, pinned) = resolve(&home, "dep", &url, &GitReference::Branch("feature".into()), Some(&first), false).unwrap(); + assert_eq!(pinned, first); + let (dir, refreshed) = resolve(&home, "dep", &url, &GitReference::Branch("feature".into()), None, false).unwrap(); assert_eq!(refreshed, advanced); assert_eq!(std::fs::read_to_string(dir.join("b.txt")).unwrap(), "feat2"); diff --git a/documentation/cli/plugins.md b/documentation/cli/plugins.md index f4f0d7a2888..a101768a794 100644 --- a/documentation/cli/plugins.md +++ b/documentation/cli/plugins.md @@ -71,7 +71,7 @@ Pre-built binaries are also available from [Leo releases](https://github.com/Pro For details on release artifacts, target platforms, and packaging guidelines, see the [Binary Distribution Reference](../guides/binary_distribution.md). -Running `leo update` will also attempt to update bundled plugins like `leo-fmt` on a best-effort basis. +To update installed plugins, run the installation command again. `leo update` changes project dependency locks; it does not install plugins. ### Writing Custom Plugins diff --git a/documentation/cli/update.md b/documentation/cli/update.md index b1f2f330c5b..3b5fadc08a5 100644 --- a/documentation/cli/update.md +++ b/documentation/cli/update.md @@ -6,48 +6,54 @@ toc_min_heading_level: 2 toc_max_heading_level: 2 --- -[general tags]: # "cli, leo_update, versioning" +[general tags]: # "cli, leo_update, dependencies" # `leo update` -To download and install the latest Leo version run: +Update dependency versions in `leo.lock` without changing `program.json`. ```bash leo update ``` -```bash title="console output:" -Checking target-arch... aarch64-apple-darwin -Checking current version... v3.1.0 -Checking latest released version... v3.1.0 - Leo -Leo is already on the latest version -``` +To update one dependency, specify its name: -`leo update` also updates bundled plugins (such as `leo-fmt`) on a best-effort basis. +```bash +leo update token.aleo +``` -If you'd like to install a specific version of Leo, you can do so by passing the `--name` flag: +To preview changes without writing the lock file, use `--dry-run`: ```bash -leo update --name v3.0.0 +leo update token.aleo --dry-run --network testnet --endpoint https://api.explorer.provable.com/v1 ``` -## Flags +Network dependencies without an exact edition in `program.json` can move to the latest edition. Git dependencies that use a branch can move to its current commit. An exact network edition, Git revision, or locked Git tag stays fixed. Change the manifest requirement to select a different exact version. + +Leo fetches and validates the selected dependencies before it writes the lock. Network entries record the program identity, network, edition, and checksum. If the bytecode changes for an edition that is already locked, the command fails. It does not replace that edition's checksum. + +The first checksum comes from the configured endpoint. It detects later changes to that edition, but it does not prove that the first response was correct. + +Normal builds use the locked versions. `leo build --no-cache` downloads those versions again and checks their checksums; it does not update them. + +## Arguments and flags + +### `NAME` -### `--list` +Optional dependency name. Omit the name to update dependencies throughout the current project or workspace, including development dependencies. Packages from the same Git source and reference update together. Other locked dependencies stay fixed unless resolution requires a change. -### `-l` +### `--dry-run` -Lists all available versions of Leo. +Show the proposed changes without writing `leo.lock` or `program.json`. -### `--name` +### `--network` -### `-n` +Select the network for network dependencies, such as `testnet` or `mainnet`. -An optional release name if you wish to install a specific version of Leo. By default, the command will look for the latest release. +### `--endpoint` -### `--quiet` +Select the endpoint used to resolve and download network dependencies. -### `-q` +## Update the Leo installation -Suppresses download logs while updating. +`leo update` updates dependency locks. To install the latest Leo release and plugins, follow the [installation instructions](https://github.com/ProvableHQ/leo#-build-guide). diff --git a/tests/expectations/cli/test_deploy/COMMANDS b/tests/expectations/cli/test_deploy/COMMANDS index b0ee888ed88..d7d5fe8cf54 100755 --- a/tests/expectations/cli/test_deploy/COMMANDS +++ b/tests/expectations/cli/test_deploy/COMMANDS @@ -55,13 +55,14 @@ echo "Remote run, execute, synthesize, and --with work without a supplied lock." # Keep an automatically generated dependency lock at edition zero for the upgrade check. "$LEO_BIN" --disable-update-check --path "$REMOTE/lock_consumer" new lock_consumer > "$REMOTE/output" 2>&1 if ! "$LEO_BIN" --disable-update-check --path "$REMOTE/lock_consumer" --home "$REMOTE/home" \ - add some_sample_leo_program --edition 0 --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" \ + add some_sample_leo_program --network --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" \ > "$REMOTE/output" 2>&1; then cat "$REMOTE/output" exit 1 fi grep -Fq '"edition": 0' "$REMOTE/lock_consumer/leo.lock" cp "$REMOTE/lock_consumer/leo.lock" "$REMOTE/original.lock" +cp "$REMOTE/lock_consumer/program.json" "$REMOTE/original.json" cp "$ALEO_FILE" "$REMOTE/lock_consumer/upgrade.aleo" $LEO_BIN --path $ALEO_FILE upgrade --no-local -y $COMMON_OPTS @@ -75,14 +76,46 @@ grep -Fq 'Upgrade confirmed!' "$REMOTE/output" test "$(curl -fsS "http://localhost:${LEO_DEVNODE_PORT:-3030}/testnet/program/some_sample_leo_program.aleo/latest_edition")" = 2 cmp "$REMOTE/lock_consumer/leo.lock" "$REMOTE/original.lock" -# An explicit edition change updates the generated dependency lock. +# A dry run reports the new edition without changing the lock or manifest. if ! "$LEO_BIN" --disable-update-check --path "$REMOTE/lock_consumer" --home "$REMOTE/home" \ - add some_sample_leo_program --edition 2 --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" \ - > "$REMOTE/output" 2>&1; then + update some_sample_leo_program --dry-run --network testnet --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" \ + --network-retries 0 > "$REMOTE/output" 2>&1; then + cat "$REMOTE/output" + exit 1 +fi +grep -Fq 'Would update some_sample_leo_program.aleo (testnet) edition 0 -> 2' "$REMOTE/output" +cmp "$REMOTE/lock_consumer/leo.lock" "$REMOTE/original.lock" +cmp "$REMOTE/lock_consumer/program.json" "$REMOTE/original.json" + +if ! "$LEO_BIN" --disable-update-check --path "$REMOTE/lock_consumer" --home "$REMOTE/home" \ + update some_sample_leo_program --network testnet --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" \ + --network-retries 0 > "$REMOTE/output" 2>&1; then cat "$REMOTE/output" exit 1 fi +grep -Fq 'Updated some_sample_leo_program.aleo (testnet) edition 0 -> 2' "$REMOTE/output" grep -Fq '"edition": 2' "$REMOTE/lock_consumer/leo.lock" -echo "Upgrade uses the current root edition, and an explicit dependency update replaces the lock." +cmp "$REMOTE/lock_consumer/program.json" "$REMOTE/original.json" + +# A manifest constraint keeps an exact edition fixed during a full update. +if ! "$LEO_BIN" --disable-update-check --path "$REMOTE/lock_consumer" --home "$REMOTE/home" \ + add some_sample_leo_program --edition 0 --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" \ + > "$REMOTE/output" 2>&1; then + cat "$REMOTE/output" + exit 1 +fi +grep -Fq '"edition": 0' "$REMOTE/lock_consumer/leo.lock" +cp "$REMOTE/lock_consumer/leo.lock" "$REMOTE/exact.lock" +cp "$REMOTE/lock_consumer/program.json" "$REMOTE/exact.json" +if ! "$LEO_BIN" --disable-update-check --path "$REMOTE/lock_consumer" --home "$REMOTE/home" \ + update --network testnet --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" \ + --network-retries 0 > "$REMOTE/output" 2>&1; then + cat "$REMOTE/output" + exit 1 +fi +grep -Fq 'No dependency updates.' "$REMOTE/output" +cmp "$REMOTE/lock_consumer/leo.lock" "$REMOTE/exact.lock" +cmp "$REMOTE/lock_consumer/program.json" "$REMOTE/exact.json" +echo "Upgrade uses the current root edition; dependency updates preserve dry runs and exact constraints." $LEO_BIN --path $ALEO_FILE execute -y main 1u32 2u32 $COMMON_OPTS diff --git a/tests/expectations/cli/test_deploy/STDOUT b/tests/expectations/cli/test_deploy/STDOUT index e1f9d0fc350..c4e6c913aeb 100644 --- a/tests/expectations/cli/test_deploy/STDOUT +++ b/tests/expectations/cli/test_deploy/STDOUT @@ -153,7 +153,7 @@ Loaded the following programs into the VM: Explored XXXXXX blocks. Transaction accepted. ✅ Upgrade confirmed! -Upgrade uses the current root edition, and an explicit dependency update replaces the lock. +Upgrade uses the current root edition; dependency updates preserve dry runs and exact constraints. 📢 Using the following consensus heights: 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 To override, pass in `--consensus-heights` or override the environment variable `CONSENSUS_VERSION_HEIGHTS`. diff --git a/tests/expectations/cli/test_git_dependency/COMMANDS b/tests/expectations/cli/test_git_dependency/COMMANDS index bb456eb80b3..ac467c7b523 100755 --- a/tests/expectations/cli/test_git_dependency/COMMANDS +++ b/tests/expectations/cli/test_git_dependency/COMMANDS @@ -86,6 +86,54 @@ echo "--- 4b. build --offline reuses the lock and cache ---" | tee /dev/stderr leo_redacted build --offline > /dev/null 2>&1 echo "offline build exit: $?" +# An ordinary build keeps the locked commit after the branch advances. +set -e +OLD_COMMIT=$(${GIT} -C "${REPO}" rev-parse HEAD) +cp leo.lock "${HOMEDIR}/before.lock" +cp program.json "${HOMEDIR}/before.json" +cp build/gitbytes/gitbytes.aleo "${HOMEDIR}/before.aleo" +sed 's/ add / sub /' "${REPO}/gitbytes.aleo" > "${OUT}" +if cmp -s "${REPO}/gitbytes.aleo" "${OUT}"; then + echo "The Git fixture must change its bytecode." >&2 + exit 1 +fi +cp "${OUT}" "${REPO}/gitbytes.aleo" +${GIT} -C "${REPO}" add gitbytes.aleo +${GIT} -C "${REPO}" commit -q -m "change bytecode" +NEW_COMMIT=$(${GIT} -C "${REPO}" rev-parse HEAD) +if ! leo_redacted build > /dev/null 2>&1; then + cat "${OUT}" "${ERR}" + exit 1 +fi +cmp leo.lock "${HOMEDIR}/before.lock" +cmp build/gitbytes/gitbytes.aleo "${HOMEDIR}/before.aleo" + +# Updating one package updates its shared Git source, but preserves the fixed tag. +if ! leo_redacted update gitlib --dry-run --network testnet > /dev/null 2>&1; then + cat "${OUT}" "${ERR}" + exit 1 +fi +grep -Fq 'Would update gitlib (default)' "${OUT}" +cmp leo.lock "${HOMEDIR}/before.lock" +cmp program.json "${HOMEDIR}/before.json" +if ! leo_redacted update gitlib --network testnet > /dev/null 2>&1; then + cat "${OUT}" "${ERR}" + exit 1 +fi +grep -Fq 'Updated gitlib (default)' "${OUT}" +test "$(grep -Fc "\"commit\": \"${NEW_COMMIT}\"" leo.lock)" = 2 +test "$(grep -Fc "\"commit\": \"${OLD_COMMIT}\"" leo.lock)" = 1 +cmp program.json "${HOMEDIR}/before.json" +cp leo.lock "${HOMEDIR}/updated.lock" +if ! leo_redacted build --offline > /dev/null 2>&1; then + cat "${OUT}" "${ERR}" + exit 1 +fi +cmp leo.lock "${HOMEDIR}/updated.lock" +cmp build/gitbytes/gitbytes.aleo "${REPO}/gitbytes.aleo" +echo "Git updates preserve dry runs, shared sources, fixed tags, and subsequent builds." +set +e + echo "--- 5. --branch without --git (should fail) ---" | tee /dev/stderr leo_redacted add gitlib --branch main echo "exit: $?" diff --git a/tests/expectations/cli/test_git_dependency/STDOUT b/tests/expectations/cli/test_git_dependency/STDOUT index b6f6f40894b..1f9b51f71c6 100644 --- a/tests/expectations/cli/test_git_dependency/STDOUT +++ b/tests/expectations/cli/test_git_dependency/STDOUT @@ -121,6 +121,7 @@ leo.lock: } --- 4b. build --offline reuses the lock and cache --- offline build exit: 0 +Git updates preserve dry runs, shared sources, fixed tags, and subsequent builds. --- 5. --branch without --git (should fail) --- exit: 2 --- 6. multiple git references (should fail) --- diff --git a/tests/tests/cli/test_deploy/COMMANDS b/tests/tests/cli/test_deploy/COMMANDS index b0ee888ed88..d7d5fe8cf54 100755 --- a/tests/tests/cli/test_deploy/COMMANDS +++ b/tests/tests/cli/test_deploy/COMMANDS @@ -55,13 +55,14 @@ echo "Remote run, execute, synthesize, and --with work without a supplied lock." # Keep an automatically generated dependency lock at edition zero for the upgrade check. "$LEO_BIN" --disable-update-check --path "$REMOTE/lock_consumer" new lock_consumer > "$REMOTE/output" 2>&1 if ! "$LEO_BIN" --disable-update-check --path "$REMOTE/lock_consumer" --home "$REMOTE/home" \ - add some_sample_leo_program --edition 0 --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" \ + add some_sample_leo_program --network --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" \ > "$REMOTE/output" 2>&1; then cat "$REMOTE/output" exit 1 fi grep -Fq '"edition": 0' "$REMOTE/lock_consumer/leo.lock" cp "$REMOTE/lock_consumer/leo.lock" "$REMOTE/original.lock" +cp "$REMOTE/lock_consumer/program.json" "$REMOTE/original.json" cp "$ALEO_FILE" "$REMOTE/lock_consumer/upgrade.aleo" $LEO_BIN --path $ALEO_FILE upgrade --no-local -y $COMMON_OPTS @@ -75,14 +76,46 @@ grep -Fq 'Upgrade confirmed!' "$REMOTE/output" test "$(curl -fsS "http://localhost:${LEO_DEVNODE_PORT:-3030}/testnet/program/some_sample_leo_program.aleo/latest_edition")" = 2 cmp "$REMOTE/lock_consumer/leo.lock" "$REMOTE/original.lock" -# An explicit edition change updates the generated dependency lock. +# A dry run reports the new edition without changing the lock or manifest. if ! "$LEO_BIN" --disable-update-check --path "$REMOTE/lock_consumer" --home "$REMOTE/home" \ - add some_sample_leo_program --edition 2 --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" \ - > "$REMOTE/output" 2>&1; then + update some_sample_leo_program --dry-run --network testnet --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" \ + --network-retries 0 > "$REMOTE/output" 2>&1; then + cat "$REMOTE/output" + exit 1 +fi +grep -Fq 'Would update some_sample_leo_program.aleo (testnet) edition 0 -> 2' "$REMOTE/output" +cmp "$REMOTE/lock_consumer/leo.lock" "$REMOTE/original.lock" +cmp "$REMOTE/lock_consumer/program.json" "$REMOTE/original.json" + +if ! "$LEO_BIN" --disable-update-check --path "$REMOTE/lock_consumer" --home "$REMOTE/home" \ + update some_sample_leo_program --network testnet --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" \ + --network-retries 0 > "$REMOTE/output" 2>&1; then cat "$REMOTE/output" exit 1 fi +grep -Fq 'Updated some_sample_leo_program.aleo (testnet) edition 0 -> 2' "$REMOTE/output" grep -Fq '"edition": 2' "$REMOTE/lock_consumer/leo.lock" -echo "Upgrade uses the current root edition, and an explicit dependency update replaces the lock." +cmp "$REMOTE/lock_consumer/program.json" "$REMOTE/original.json" + +# A manifest constraint keeps an exact edition fixed during a full update. +if ! "$LEO_BIN" --disable-update-check --path "$REMOTE/lock_consumer" --home "$REMOTE/home" \ + add some_sample_leo_program --edition 0 --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" \ + > "$REMOTE/output" 2>&1; then + cat "$REMOTE/output" + exit 1 +fi +grep -Fq '"edition": 0' "$REMOTE/lock_consumer/leo.lock" +cp "$REMOTE/lock_consumer/leo.lock" "$REMOTE/exact.lock" +cp "$REMOTE/lock_consumer/program.json" "$REMOTE/exact.json" +if ! "$LEO_BIN" --disable-update-check --path "$REMOTE/lock_consumer" --home "$REMOTE/home" \ + update --network testnet --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" \ + --network-retries 0 > "$REMOTE/output" 2>&1; then + cat "$REMOTE/output" + exit 1 +fi +grep -Fq 'No dependency updates.' "$REMOTE/output" +cmp "$REMOTE/lock_consumer/leo.lock" "$REMOTE/exact.lock" +cmp "$REMOTE/lock_consumer/program.json" "$REMOTE/exact.json" +echo "Upgrade uses the current root edition; dependency updates preserve dry runs and exact constraints." $LEO_BIN --path $ALEO_FILE execute -y main 1u32 2u32 $COMMON_OPTS diff --git a/tests/tests/cli/test_git_dependency/COMMANDS b/tests/tests/cli/test_git_dependency/COMMANDS index bb456eb80b3..ac467c7b523 100755 --- a/tests/tests/cli/test_git_dependency/COMMANDS +++ b/tests/tests/cli/test_git_dependency/COMMANDS @@ -86,6 +86,54 @@ echo "--- 4b. build --offline reuses the lock and cache ---" | tee /dev/stderr leo_redacted build --offline > /dev/null 2>&1 echo "offline build exit: $?" +# An ordinary build keeps the locked commit after the branch advances. +set -e +OLD_COMMIT=$(${GIT} -C "${REPO}" rev-parse HEAD) +cp leo.lock "${HOMEDIR}/before.lock" +cp program.json "${HOMEDIR}/before.json" +cp build/gitbytes/gitbytes.aleo "${HOMEDIR}/before.aleo" +sed 's/ add / sub /' "${REPO}/gitbytes.aleo" > "${OUT}" +if cmp -s "${REPO}/gitbytes.aleo" "${OUT}"; then + echo "The Git fixture must change its bytecode." >&2 + exit 1 +fi +cp "${OUT}" "${REPO}/gitbytes.aleo" +${GIT} -C "${REPO}" add gitbytes.aleo +${GIT} -C "${REPO}" commit -q -m "change bytecode" +NEW_COMMIT=$(${GIT} -C "${REPO}" rev-parse HEAD) +if ! leo_redacted build > /dev/null 2>&1; then + cat "${OUT}" "${ERR}" + exit 1 +fi +cmp leo.lock "${HOMEDIR}/before.lock" +cmp build/gitbytes/gitbytes.aleo "${HOMEDIR}/before.aleo" + +# Updating one package updates its shared Git source, but preserves the fixed tag. +if ! leo_redacted update gitlib --dry-run --network testnet > /dev/null 2>&1; then + cat "${OUT}" "${ERR}" + exit 1 +fi +grep -Fq 'Would update gitlib (default)' "${OUT}" +cmp leo.lock "${HOMEDIR}/before.lock" +cmp program.json "${HOMEDIR}/before.json" +if ! leo_redacted update gitlib --network testnet > /dev/null 2>&1; then + cat "${OUT}" "${ERR}" + exit 1 +fi +grep -Fq 'Updated gitlib (default)' "${OUT}" +test "$(grep -Fc "\"commit\": \"${NEW_COMMIT}\"" leo.lock)" = 2 +test "$(grep -Fc "\"commit\": \"${OLD_COMMIT}\"" leo.lock)" = 1 +cmp program.json "${HOMEDIR}/before.json" +cp leo.lock "${HOMEDIR}/updated.lock" +if ! leo_redacted build --offline > /dev/null 2>&1; then + cat "${OUT}" "${ERR}" + exit 1 +fi +cmp leo.lock "${HOMEDIR}/updated.lock" +cmp build/gitbytes/gitbytes.aleo "${REPO}/gitbytes.aleo" +echo "Git updates preserve dry runs, shared sources, fixed tags, and subsequent builds." +set +e + echo "--- 5. --branch without --git (should fail) ---" | tee /dev/stderr leo_redacted add gitlib --branch main echo "exit: $?" From 8c81ca26759bd279ac1f405c83aa30c64802e747 Mon Sep 17 00:00:00 2001 From: Mohammad Fawaz Date: Wed, 30 Sep 2026 16:54:10 -0400 Subject: [PATCH 07/10] Remove automatic Leo release checks and updater dependencies --- Cargo.lock | 286 +------------------------- Cargo.toml | 1 - crates/leo/Cargo.toml | 1 - crates/leo/src/cli/cli.rs | 11 +- crates/leo/src/cli/helpers/mod.rs | 1 - crates/leo/src/cli/helpers/updater.rs | 195 ------------------ crates/leo/src/errors.rs | 14 -- documentation/cli/overview.md | 4 - documentation/cli/update.md | 2 +- 9 files changed, 14 insertions(+), 501 deletions(-) delete mode 100644 crates/leo/src/cli/helpers/updater.rs diff --git a/Cargo.lock b/Cargo.lock index 92a9001ff9a..2c7f5c70d64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -186,15 +186,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "arbitrary" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" -dependencies = [ - "derive_arbitrary", -] - [[package]] name = "arc-swap" version = "1.9.2" @@ -814,12 +805,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - [[package]] name = "const-oid" version = "0.10.2" @@ -1068,33 +1053,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "curve25519-dalek" -version = "4.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "curve25519-dalek-derive", - "digest 0.10.7", - "fiat-crypto", - "rustc_version", - "subtle", - "zeroize", -] - -[[package]] -name = "curve25519-dalek-derive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" -dependencies = [ - "proc-macro2", - "quote 1.0.47", - "syn 2.0.119", -] - [[package]] name = "dashmap" version = "6.2.1" @@ -1140,23 +1098,13 @@ dependencies = [ "thiserror 2.0.19", ] -[[package]] -name = "der" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" -dependencies = [ - "const-oid 0.9.6", - "zeroize", -] - [[package]] name = "der" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a878c850e9e421b20262e9b41f9c860e4785fa07541c266b62ff9d1ef998a80a" dependencies = [ - "const-oid 0.10.2", + "const-oid", "pem-rfc7468", "zeroize", ] @@ -1178,17 +1126,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "derive_arbitrary" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" -dependencies = [ - "proc-macro2", - "quote 1.0.47", - "syn 2.0.119", -] - [[package]] name = "derive_more" version = "2.1.1" @@ -1241,7 +1178,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "block-buffer 0.12.1", - "const-oid 0.10.2", + "const-oid", "crypto-common 0.2.2", "ctutils", ] @@ -1322,36 +1259,11 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0681a4fc24c767085329728d8dfba959af91228aa4610cca4f8ce317ba46ae0" dependencies = [ - "der 0.8.2", + "der", "digest 0.11.3", "elliptic-curve", "rfc6979", - "signature 3.0.0", - "zeroize", -] - -[[package]] -name = "ed25519" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" -dependencies = [ - "pkcs8", - "signature 2.2.0", -] - -[[package]] -name = "ed25519-dalek" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" -dependencies = [ - "curve25519-dalek", - "ed25519", - "serde", - "sha2 0.10.9", - "signature 2.2.0", - "subtle", + "signature", "zeroize", ] @@ -1547,12 +1459,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "fiat-crypto" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" - [[package]] name = "filetime" version = "0.2.29" @@ -1577,7 +1483,6 @@ checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", - "zlib-rs", ] [[package]] @@ -3158,19 +3063,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "indicatif" -version = "0.18.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9433806cd6b4ec1aba79c021c7e4c58fb4c3b9977c085062e611ac929998fb0c" -dependencies = [ - "console", - "portable-atomic", - "unicode-width", - "unit-prefix", - "web-time", -] - [[package]] name = "io-close" version = "0.3.7" @@ -3370,7 +3262,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "primeorder", - "sha2 0.11.0", + "sha2", "wnaf", ] @@ -3574,11 +3466,10 @@ dependencies = [ "reqwest", "rocksdb", "rpassword", - "self_update", "serde", "serde_json", "serial_test", - "sha2 0.11.0", + "sha2", "signal-hook 0.4.4", "similar", "snarkvm", @@ -3686,7 +3577,7 @@ dependencies = [ "serde", "serde_json", "serial_test", - "sha2 0.11.0", + "sha2", "snarkvm", ] @@ -4257,16 +4148,6 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der 0.7.10", - "spki", -] - [[package]] name = "pkg-config" version = "0.3.33" @@ -4400,15 +4281,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "quick-xml" -version = "0.38.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" -dependencies = [ - "memchr", -] - [[package]] name = "quinn" version = "0.11.11" @@ -4534,15 +4406,6 @@ dependencies = [ "rand_core 0.10.1", ] -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.17", -] - [[package]] name = "rand_core" version = "0.9.5" @@ -4933,7 +4796,7 @@ checksum = "d56d437c2f19203ce5f7122e507831de96f3d2d4d3be5af44a0b0a09d8a80e4d" dependencies = [ "base16ct", "ctutils", - "der 0.8.2", + "der", "hybrid-array", "subtle", "zeroize", @@ -4962,40 +4825,6 @@ dependencies = [ "libc", ] -[[package]] -name = "self-replace" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ec815b5eab420ab893f63393878d89c90fdd94c0bcc44c07abb8ad95552fb7" -dependencies = [ - "fastrand", - "tempfile", - "windows-sys 0.52.0", -] - -[[package]] -name = "self_update" -version = "0.44.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e79722b5a505d4ddc77527455a97244e9e8c4c07533ff44cf4421cce7bb6d17" -dependencies = [ - "http", - "indicatif", - "log", - "quick-xml", - "regex", - "reqwest", - "self-replace", - "semver", - "serde", - "serde_json", - "tempfile", - "ureq", - "urlencoding", - "zip", - "zipsign-api", -] - [[package]] name = "semver" version = "1.0.28" @@ -5132,17 +4961,6 @@ dependencies = [ "sha1", ] -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "digest 0.10.7", -] - [[package]] name = "sha2" version = "0.11.0" @@ -5222,16 +5040,6 @@ dependencies = [ "libc", ] -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest 0.10.7", - "rand_core 0.6.4", -] - [[package]] name = "signature" version = "3.0.0" @@ -5334,7 +5142,7 @@ dependencies = [ "rand 0.10.2", "rayon", "serde", - "sha2 0.11.0", + "sha2", "smallvec", "snarkvm-curves", "snarkvm-fields", @@ -6079,7 +5887,7 @@ dependencies = [ "paste", "rand 0.10.2", "serde_json", - "sha2 0.11.0", + "sha2", "snarkvm-curves", "snarkvm-utilities", "thiserror 2.0.19", @@ -6244,17 +6052,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "socks" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c3dbbd9ae980613c6dd8e28a9407b50509d3803b57624d5dfe8315218cd58b" -dependencies = [ - "byteorder", - "libc", - "winapi", -] - [[package]] name = "spinning_top" version = "0.3.0" @@ -6264,16 +6061,6 @@ dependencies = [ "lock_api", ] -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der 0.7.10", -] - [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -6900,12 +6687,6 @@ version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" -[[package]] -name = "unit-prefix" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" - [[package]] name = "untrusted" version = "0.9.0" @@ -6920,8 +6701,7 @@ checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0" dependencies = [ "base64", "cookie_store", - "der 0.8.2", - "encoding_rs", + "der", "flate2", "log", "native-tls", @@ -6930,7 +6710,6 @@ dependencies = [ "rustls-pki-types", "serde", "serde_json", - "socks", "ureq-proto", "utf8-zero", "webpki-root-certs", @@ -6961,12 +6740,6 @@ dependencies = [ "serde", ] -[[package]] -name = "urlencoding" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" - [[package]] name = "utf8-zero" version = "0.8.1" @@ -7484,31 +7257,6 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "zip" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2a05c7c36fde6c09b08576c9f7fb4cda705990f73b58fe011abf7dfb24168b" -dependencies = [ - "arbitrary", - "crc32fast", - "flate2", - "indexmap", - "memchr", - "time", - "zopfli", -] - -[[package]] -name = "zipsign-api" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dba6063ff82cdbd9a765add16d369abe81e520f836054e997c2db217ceca40c0" -dependencies = [ - "ed25519-dalek", - "thiserror 2.0.19", -] - [[package]] name = "zlib-rs" version = "0.6.7" @@ -7520,15 +7268,3 @@ name = "zmij" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" - -[[package]] -name = "zopfli" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" -dependencies = [ - "bumpalo", - "crc32fast", - "log", - "simd-adler32", -] diff --git a/Cargo.toml b/Cargo.toml index bd0164129bc..6ab6a1e64d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,7 +73,6 @@ reqwest = { version = "0.13", features = ["blocking", "json"] } rowan = "0.16" rpassword = "7.5" scoped-tls = "1.0" -self_update = { version = "0.44", features = [ "archive-zip", "compression-zip-deflate" ] } serde = { version = "1.0", features = [ "derive", "rc" ] } serde_json = { version = "1.0", features = [ "preserve_order" ] } serial_test = "3.5" diff --git a/crates/leo/Cargo.toml b/crates/leo/Cargo.toml index 34eaf0254f5..bee66bd5e0d 100644 --- a/crates/leo/Cargo.toml +++ b/crates/leo/Cargo.toml @@ -85,7 +85,6 @@ rayon = { workspace = true } regex = { workspace = true } reqwest = { workspace = true } rpassword = { workspace = true } -self_update = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } serial_test = { workspace = true } diff --git a/crates/leo/src/cli/cli.rs b/crates/leo/src/cli/cli.rs index e722a604a10..891ac2b897c 100644 --- a/crates/leo/src/cli/cli.rs +++ b/crates/leo/src/cli/cli.rs @@ -33,7 +33,8 @@ pub struct CLI { #[clap(long, global = true, help = "Write command results as JSON. Pass `--json-output=` for a custom path; with no value it defaults to build/json-outputs/.json.", num_args = 0..=1, require_equals = true, default_missing_value = "")] json_output: Option, - #[clap(long, global = true, help = "Disable Leo's daily check for version updates")] + // Accept the old flag so existing scripts continue to work. + #[clap(long, global = true, hide = true)] disable_update_check: bool, #[clap(subcommand)] @@ -226,14 +227,6 @@ pub fn run_with_args(cli: CLI) -> Result<()> { })?; } - // Check for updates. If not forced, it checks once per day. - if !quiet - && !cli.disable_update_check - && let Ok(true) = updater::Updater::check_for_updates(false) - { - let _ = updater::Updater::print_cli(); - } - // Get custom root folder and create context for it. // If not specified, default context will be created in cwd. let context = handle_error(Context::new(cli.path.clone(), cli.home, false, cli.package.clone())); diff --git a/crates/leo/src/cli/helpers/mod.rs b/crates/leo/src/cli/helpers/mod.rs index c36ba4218e7..bc2e93cd335 100644 --- a/crates/leo/src/cli/helpers/mod.rs +++ b/crates/leo/src/cli/helpers/mod.rs @@ -17,4 +17,3 @@ pub mod check_transaction; pub mod context; pub mod logger; -pub mod updater; diff --git a/crates/leo/src/cli/helpers/updater.rs b/crates/leo/src/cli/helpers/updater.rs deleted file mode 100644 index a94dfde9e5c..00000000000 --- a/crates/leo/src/cli/helpers/updater.rs +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright (C) 2019-2026 Provable Inc. -// This file is part of the Leo library. - -// The Leo library is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// The Leo library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with the Leo library. If not, see . - -use leo_errors::{Backtraced, Result}; - -use aleo_std; - -use colored::Colorize; -use self_update::{backends::github, version::bump_is_greater}; -use std::{ - fs, - path::{Path, PathBuf}, - time::{Duration, SystemTime, UNIX_EPOCH}, -}; - -pub struct Updater; - -impl Updater { - const LEO_BIN_NAME: &'static str = "leo"; - const LEO_CACHE_LAST_CHECK_FILE: &'static str = "leo_cache_last_update_check"; - const LEO_CACHE_VERSION_FILE: &'static str = "leo_cache_latest_version"; - const LEO_REPO_NAME: &'static str = "leo"; - const LEO_REPO_OWNER: &'static str = "ProvableHQ"; - // 24 hours - const LEO_UPDATE_CHECK_INTERVAL: Duration = Duration::from_secs(24 * 60 * 60); - - /// Check if there is an available update for `leo` and return the newest release. - pub fn update_available() -> Result { - let updater = github::Update::configure() - .repo_owner(Self::LEO_REPO_OWNER) - .repo_name(Self::LEO_REPO_NAME) - .bin_name(Self::LEO_BIN_NAME) - .current_version(env!("CARGO_PKG_VERSION")) - .build() - .map_err(crate::errors::self_update_error)?; - - let current_version = updater.current_version(); - let latest_release = updater.get_latest_release().map_err(crate::errors::self_update_error)?; - - if bump_is_greater(¤t_version, &latest_release.version).map_err(crate::errors::self_update_error)? { - Ok(latest_release.version) - } else { - Err(crate::errors::old_release_version(current_version, latest_release.version).into()) - } - } - - /// Read the latest version from the version file. - pub fn read_latest_version() -> Result, Backtraced> { - let version_file_path = Self::get_version_file_path(); - match fs::read_to_string(version_file_path) { - Ok(version) => Ok(Some(version.trim().to_string())), - Err(_) => Ok(None), - } - } - - /// Generate the CLI message if a new version is available. - pub fn get_cli_string() -> Result, Backtraced> { - if let Some(latest_version) = Self::read_latest_version()? { - let colorized_message = format!( - "\n🟢 {} {} {}", - format!("Leo v{latest_version} is available.").bold().green(), - "For installation instructions, see".bold().green(), - "https://github.com/ProvableHQ/leo#-build-guide".bold().white() - ); - Ok(Some(colorized_message)) - } else { - Ok(None) - } - } - - /// Display the CLI message if a new version is available. - pub fn print_cli() -> Result<(), Backtraced> { - if let Some(message) = Self::get_cli_string()? { - println!("{message}"); - } - Ok(()) - } - - /// Check for updates, respecting the update interval. (Currently once per day.) - /// If a new version is found, write it to a cache file and alert in every call. - pub fn check_for_updates(force: bool) -> Result { - // Get the cache directory and relevant file paths. - let cache_dir = Self::get_cache_dir(); - let last_check_file = cache_dir.join(Self::LEO_CACHE_LAST_CHECK_FILE); - let version_file = Self::get_version_file_path(); - - // Determine if we should check for updates. - let should_check = force || Self::should_check_for_updates(&last_check_file)?; - - if should_check { - match Self::update_available() { - Ok(latest_version) => { - // A new version is available - Self::update_check_files(&cache_dir, &last_check_file, &version_file, &latest_version)?; - Ok(true) - } - Err(_) => { - // No new version available or error occurred - // We'll treat both cases as "no update" for simplicity - Self::update_check_files(&cache_dir, &last_check_file, &version_file, env!("CARGO_PKG_VERSION"))?; - Ok(false) - } - } - } else if version_file.exists() { - if let Ok(stored_version) = fs::read_to_string(&version_file) { - let current_version = env!("CARGO_PKG_VERSION"); - Ok(bump_is_greater(current_version, stored_version.trim()).map_err(crate::errors::self_update_error)?) - } else { - // If we can't read the file, assume no update is available - Ok(false) - } - } else { - Ok(false) - } - } - - /// Updates the check files with the latest version information and timestamp. - /// - /// This function creates the cache directory if it doesn't exist, writes the current time - /// to the last check file, and writes the latest version to the version file. - fn update_check_files( - cache_dir: &Path, - last_check_file: &Path, - version_file: &Path, - latest_version: &str, - ) -> Result<(), Backtraced> { - // Recursively create the cache directory and all of its parent components if they are missing. - fs::create_dir_all(cache_dir).map_err(crate::errors::cli_io_error)?; - - // Get the current time. - let current_time = Self::get_current_time()?; - - // Write the current time to the last check file. - fs::write(last_check_file, current_time.to_string()).map_err(crate::errors::cli_io_error)?; - - // Write the latest version to the version file. - fs::write(version_file, latest_version).map_err(crate::errors::cli_io_error)?; - - Ok(()) - } - - /// Determines if an update check should be performed based on the last check time. - /// - /// This function reads the last check timestamp from a file and compares it with - /// the current time to decide if enough time has passed for a new check. - fn should_check_for_updates(last_check_file: &Path) -> Result { - match fs::read_to_string(last_check_file) { - Ok(contents) => { - // Parse the last check timestamp from the file. - let last_check = contents - .parse::() - .map_err(|e| crate::errors::cli_runtime_error(format!("Failed to parse last check time: {e}")))?; - - // Get the current time. - let current_time = Self::get_current_time()?; - - // Check if enough time has passed since the last check. - Ok(current_time.saturating_sub(last_check) > Self::LEO_UPDATE_CHECK_INTERVAL.as_secs()) - } - // If we can't read the file, assume we should check - Err(_) => Ok(true), - } - } - - /// Gets the current system time as seconds since the Unix epoch. - fn get_current_time() -> Result { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .map_err(|e| crate::errors::cli_runtime_error(format!("System time error: {e}"))) - .map(|duration| duration.as_secs()) - } - - /// Get the path to the file storing the latest version information. - fn get_version_file_path() -> PathBuf { - Self::get_cache_dir().join(Self::LEO_CACHE_VERSION_FILE) - } - - /// Get the cache directory for Leo. - fn get_cache_dir() -> PathBuf { - aleo_std::aleo_dir().join("leo") - } -} diff --git a/crates/leo/src/errors.rs b/crates/leo/src/errors.rs index 77d585cd470..b23e100f3d3 100644 --- a/crates/leo/src/errors.rs +++ b/crates/leo/src/errors.rs @@ -35,20 +35,6 @@ pub(crate) fn cli_runtime_error(error: impl Display) -> Backtraced { Backtraced::error(CODE_PREFIX, CODE_MASK + 2, format!("CLI runtime error: {error}")) } -pub(crate) fn self_update_error(error: impl ErrorArg) -> Backtraced { - Backtraced::error(CODE_PREFIX, CODE_MASK + 5, format!("Leo release check failed: {error}")) - .with_help("Check the available releases at https://github.com/ProvableHQ/leo/releases.") -} - -pub(crate) fn old_release_version(current: impl Display, latest: impl Display) -> Backtraced { - Backtraced::error( - CODE_PREFIX, - CODE_MASK + 7, - format!("No newer Leo release is available (installed: `{current}`, latest: `{latest}`)"), - ) - .with_help("For installation instructions, see https://github.com/ProvableHQ/leo#-build-guide.") -} - pub(crate) fn failed_to_load_instructions(error: impl Display) -> Backtraced { Backtraced::error( CODE_PREFIX, diff --git a/documentation/cli/overview.md b/documentation/cli/overview.md index e5ce7241111..d0e771a5d18 100644 --- a/documentation/cli/overview.md +++ b/documentation/cli/overview.md @@ -69,10 +69,6 @@ Suppresses the CLI output. Prints out additional information for debugging if possible. -### `--disable-update-check` - -Disables Leo's daily check for CLI version updates. - ### `--path ` Specifies the path to Leo program root folder. Defaults to `./`. diff --git a/documentation/cli/update.md b/documentation/cli/update.md index 3b5fadc08a5..6d96337173e 100644 --- a/documentation/cli/update.md +++ b/documentation/cli/update.md @@ -56,4 +56,4 @@ Select the endpoint used to resolve and download network dependencies. ## Update the Leo installation -`leo update` updates dependency locks. To install the latest Leo release and plugins, follow the [installation instructions](https://github.com/ProvableHQ/leo#-build-guide). +`leo update` updates dependency locks. Leo does not check for new releases or show release notices. To install the latest Leo release and plugins, follow the [installation instructions](https://github.com/ProvableHQ/leo#-build-guide). From f3f48fbf6ce7197c85a5a795d606a4c829fdb50b Mon Sep 17 00:00:00 2001 From: Mohammad Fawaz Date: Wed, 30 Sep 2026 17:05:11 -0400 Subject: [PATCH 08/10] Remove obsolete update-check flag and refresh dependency docs --- .circleci/continue_config.yml | 4 +- crates/leo/src/cli/cli.rs | 61 +++---------------- documentation/cli/add.md | 8 +-- documentation/cli/build.md | 6 +- documentation/cli/deploy.md | 2 +- documentation/cli/execute.md | 2 +- documentation/cli/overview.md | 4 +- documentation/cli/run.md | 2 +- documentation/cli/test.md | 2 +- documentation/cli/update.md | 4 +- documentation/cli/upgrade.md | 4 +- .../dependencies/git_dep/leo.lock | 2 +- documentation/getting_started/installation.md | 10 +++ documentation/guides/dependencies.md | 55 ++++++++++------- documentation/guides/deploying.md | 2 +- documentation/guides/workspaces.md | 4 ++ documentation/language/layout.md | 2 +- .../cli/aleo_dep_imports_local_dep/COMMANDS | 2 +- .../cli/aleo_dep_reversed_order/COMMANDS | 2 +- .../expectations/cli/broadcast_error/COMMANDS | 2 +- .../expectations/cli/build_checksums/COMMANDS | 4 +- .../cli/build_missing_constructor/COMMANDS | 2 +- .../leo_devnode_missing_private_key/COMMANDS | 2 +- .../cli/local_aleo_dependency/COMMANDS | 2 +- .../cli/multiple_leo_deps/COMMANDS | 2 +- .../cli/network_dependency/COMMANDS | 2 +- .../cli/program_name_mismatch/COMMANDS | 2 +- .../expectations/cli/test_abi_compat/COMMANDS | 18 +++--- .../cli/test_abi_comprehensive/COMMANDS | 2 +- .../cli/test_abi_from_aleo/COMMANDS | 12 ++-- tests/expectations/cli/test_add/COMMANDS | 8 +-- .../cli/test_add_dependency/COMMANDS | 22 +++---- .../cli/test_add_library_to_program/COMMANDS | 4 +- .../cli/test_add_workspace/COMMANDS | 8 +-- .../cli/test_ambiguous_package/COMMANDS | 2 +- .../cli/test_command_shortcuts/COMMANDS | 4 +- .../test_dependency_visible_to_tests/COMMANDS | 2 +- tests/expectations/cli/test_deploy/COMMANDS | 20 +++--- .../cli/test_deploy_rename/COMMANDS | 4 +- .../cli/test_deploy_rename_conflict/COMMANDS | 2 +- .../cli/test_deploy_rename_rejected/COMMANDS | 2 +- .../COMMANDS | 4 +- .../COMMANDS | 2 +- tests/expectations/cli/test_devnet/COMMANDS | 4 +- tests/expectations/cli/test_devnode/COMMANDS | 4 +- .../test_devnode_persistent_storage/COMMANDS | 6 +- .../cli/test_devnode_view/COMMANDS | 2 +- .../cli/test_duplicate_lib_name/COMMANDS | 2 +- .../cli/test_dynamic_call_with_flag/COMMANDS | 9 ++- .../cli/test_execute_view_rejected/COMMANDS | 2 +- .../test_external_constructor_ctx/COMMANDS | 4 +- .../test_external_program_submodule/COMMANDS | 2 +- .../test_external_toplevel_closure/COMMANDS | 2 +- .../cli/test_failure_exit_code/COMMANDS | 2 +- tests/expectations/cli/test_fmt/COMMANDS | 12 ++-- .../cli/test_git_dependency/COMMANDS | 4 +- .../cli/test_identifier_input/COMMANDS | 4 +- .../test_interface_abi_from_library/COMMANDS | 4 +- .../test_interface_abi_inheritance/COMMANDS | 2 +- .../cli/test_interface_abi_local/COMMANDS | 2 +- .../test_invalid_literal_rejection/COMMANDS | 11 ++-- tests/expectations/cli/test_json/COMMANDS | 10 +-- .../cli/test_lib_program_test/COMMANDS | 2 +- .../test_lib_submodule_with_tests/COMMANDS | 2 +- .../cli/test_lib_with_tests/COMMANDS | 2 +- tests/expectations/cli/test_library/COMMANDS | 22 +++---- .../cli/test_library_build/COMMANDS | 4 +- .../COMMANDS | 2 +- .../test_library_build_type_error/COMMANDS | 2 +- .../COMMANDS | 2 +- .../COMMANDS | 2 +- .../test_library_build_with_dep_lib/COMMANDS | 4 +- .../cli/test_library_circular/COMMANDS | 6 +- .../cli/test_library_parse_error/COMMANDS | 4 +- .../test_library_submodule_access/COMMANDS | 4 +- .../cli/test_multiple_test_files/COMMANDS | 2 +- .../cli/test_network_retries/COMMANDS | 4 +- .../cli/test_network_retries_flag/COMMANDS | 2 +- .../cli/test_run_view_rejected/COMMANDS | 5 +- .../cli/test_simple_build/COMMANDS | 2 +- .../cli/test_simple_test/COMMANDS | 2 +- .../cli/test_storage_from_unit_test/COMMANDS | 2 +- tests/expectations/cli/test_version/COMMANDS | 2 +- .../cli/test_with_record/COMMANDS | 2 +- .../cli/test_workspace_deploy/COMMANDS | 2 +- .../cli/aleo_dep_imports_local_dep/COMMANDS | 2 +- .../cli/aleo_dep_reversed_order/COMMANDS | 2 +- tests/tests/cli/broadcast_error/COMMANDS | 2 +- tests/tests/cli/build_checksums/COMMANDS | 4 +- .../cli/build_missing_constructor/COMMANDS | 2 +- .../leo_devnode_missing_private_key/COMMANDS | 2 +- .../tests/cli/local_aleo_dependency/COMMANDS | 2 +- tests/tests/cli/multiple_leo_deps/COMMANDS | 2 +- tests/tests/cli/network_dependency/COMMANDS | 2 +- .../tests/cli/program_name_mismatch/COMMANDS | 2 +- tests/tests/cli/test_abi_compat/COMMANDS | 18 +++--- .../tests/cli/test_abi_comprehensive/COMMANDS | 2 +- tests/tests/cli/test_abi_from_aleo/COMMANDS | 12 ++-- tests/tests/cli/test_add/COMMANDS | 8 +-- tests/tests/cli/test_add_dependency/COMMANDS | 22 +++---- .../cli/test_add_library_to_program/COMMANDS | 4 +- tests/tests/cli/test_add_workspace/COMMANDS | 8 +-- .../tests/cli/test_ambiguous_package/COMMANDS | 2 +- .../tests/cli/test_command_shortcuts/COMMANDS | 4 +- .../test_dependency_visible_to_tests/COMMANDS | 2 +- tests/tests/cli/test_deploy/COMMANDS | 20 +++--- tests/tests/cli/test_deploy_rename/COMMANDS | 4 +- .../cli/test_deploy_rename_conflict/COMMANDS | 2 +- .../cli/test_deploy_rename_rejected/COMMANDS | 2 +- .../COMMANDS | 4 +- .../COMMANDS | 2 +- tests/tests/cli/test_devnet/COMMANDS | 4 +- tests/tests/cli/test_devnode/COMMANDS | 4 +- .../test_devnode_persistent_storage/COMMANDS | 6 +- tests/tests/cli/test_devnode_view/COMMANDS | 2 +- .../cli/test_duplicate_lib_name/COMMANDS | 2 +- .../cli/test_dynamic_call_with_flag/COMMANDS | 9 ++- .../cli/test_execute_view_rejected/COMMANDS | 2 +- .../test_external_constructor_ctx/COMMANDS | 4 +- .../test_external_program_submodule/COMMANDS | 2 +- .../test_external_toplevel_closure/COMMANDS | 2 +- .../tests/cli/test_failure_exit_code/COMMANDS | 2 +- tests/tests/cli/test_fmt/COMMANDS | 12 ++-- tests/tests/cli/test_git_dependency/COMMANDS | 4 +- .../tests/cli/test_identifier_input/COMMANDS | 4 +- .../test_interface_abi_from_library/COMMANDS | 4 +- .../test_interface_abi_inheritance/COMMANDS | 2 +- .../cli/test_interface_abi_local/COMMANDS | 2 +- .../test_invalid_literal_rejection/COMMANDS | 11 ++-- tests/tests/cli/test_json/COMMANDS | 10 +-- .../tests/cli/test_lib_program_test/COMMANDS | 2 +- .../test_lib_submodule_with_tests/COMMANDS | 2 +- tests/tests/cli/test_lib_with_tests/COMMANDS | 2 +- tests/tests/cli/test_library/COMMANDS | 22 +++---- tests/tests/cli/test_library_build/COMMANDS | 4 +- .../COMMANDS | 2 +- .../test_library_build_type_error/COMMANDS | 2 +- .../COMMANDS | 2 +- .../COMMANDS | 2 +- .../test_library_build_with_dep_lib/COMMANDS | 4 +- .../tests/cli/test_library_circular/COMMANDS | 6 +- .../cli/test_library_parse_error/COMMANDS | 4 +- .../test_library_submodule_access/COMMANDS | 4 +- .../cli/test_multiple_test_files/COMMANDS | 2 +- tests/tests/cli/test_network_retries/COMMANDS | 4 +- .../cli/test_network_retries_flag/COMMANDS | 2 +- .../tests/cli/test_run_view_rejected/COMMANDS | 5 +- tests/tests/cli/test_simple_build/COMMANDS | 2 +- tests/tests/cli/test_simple_test/COMMANDS | 2 +- .../cli/test_storage_from_unit_test/COMMANDS | 2 +- tests/tests/cli/test_version/COMMANDS | 2 +- tests/tests/cli/test_with_record/COMMANDS | 2 +- .../tests/cli/test_workspace_deploy/COMMANDS | 2 +- 153 files changed, 384 insertions(+), 408 deletions(-) diff --git a/.circleci/continue_config.yml b/.circleci/continue_config.yml index 88b89c16fc9..f592b887638 100755 --- a/.circleci/continue_config.yml +++ b/.circleci/continue_config.yml @@ -663,9 +663,9 @@ jobs: while IFS= read -r d; do echo "--- $d ---" if [ -d "$d/tests" ]; then - (cd "$d" && "$LEO" --disable-update-check test) || failed=1 + (cd "$d" && "$LEO" test) || failed=1 else - (cd "$d" && "$LEO" --disable-update-check build) || failed=1 + (cd "$d" && "$LEO" build) || failed=1 fi done < <(find documentation/code_snippets -name program.json -not -path '*/build/*' -not -path '*/imports/*' | xargs -n1 dirname | sort) exit $failed diff --git a/crates/leo/src/cli/cli.rs b/crates/leo/src/cli/cli.rs index 891ac2b897c..7201c3688b2 100644 --- a/crates/leo/src/cli/cli.rs +++ b/crates/leo/src/cli/cli.rs @@ -33,10 +33,6 @@ pub struct CLI { #[clap(long, global = true, help = "Write command results as JSON. Pass `--json-output=` for a custom path; with no value it defaults to build/json-outputs/.json.", num_args = 0..=1, require_equals = true, default_missing_value = "")] json_output: Option, - // Accept the old flag so existing scripts continue to work. - #[clap(long, global = true, hide = true)] - disable_update_check: bool, - #[clap(subcommand)] command: Commands, @@ -342,7 +338,12 @@ mod tests { ]] { CLI::try_parse_from(arguments).expect("Dependency update arguments must parse"); } - for arguments in [vec!["leo", "update", "--list"], vec!["leo", "update", "--name", "v4.4.4"]] { + for arguments in [ + vec!["leo", "update", "--list"], + vec!["leo", "update", "--name", "v4.4.4"], + vec!["leo", "--disable-update-check", "build"], + vec!["leo", "update", "--disable-update-check"], + ] { assert!(CLI::try_parse_from(arguments).is_err(), "Self-update flags must be removed"); } } @@ -593,7 +594,6 @@ mod tests { let cli = CLI::try_parse_from([ "leo", "-q", - "--disable-update-check", "--path", project.to_str().expect("The project path must be UTF-8"), "--home", @@ -639,7 +639,6 @@ mod tests { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::New { command: LeoNew { name: "add_missing_network_dep".to_string(), library: false, workspace: false }, }, @@ -652,7 +651,6 @@ mod tests { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::Add { command: LeoAdd { name: "nonexistent_program".to_string(), @@ -705,7 +703,6 @@ mod tests { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::Run { command: crate::cli::commands::LeoRun { name: "example".to_string(), @@ -753,7 +750,6 @@ mod tests { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::Run { command: crate::cli::commands::LeoRun { name: "double_wrapper_mint".to_string(), @@ -802,7 +798,6 @@ mod tests { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::Run { command: crate::cli::commands::LeoRun { name: "inner_1_main".to_string(), @@ -845,7 +840,6 @@ mod tests { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::Run { command: crate::cli::commands::LeoRun { name: "main".to_string(), @@ -884,7 +878,6 @@ mod tests { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::New { command: crate::cli::commands::LeoNew { name: lib_name.to_string(), library: true, workspace: false }, }, @@ -930,7 +923,6 @@ mod tests { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::New { command: crate::cli::commands::LeoNew { name: ws_name.to_string(), library: false, workspace: true }, }, @@ -1031,7 +1023,7 @@ mod tests { ("test", "failed to load Leo project"), ("clean", "doesn't appear to be a Leo package"), ] { - let cli = CLI::try_parse_from(["leo", "-q", "--disable-update-check", "--path", aleo_path, command]) + let cli = CLI::try_parse_from(["leo", "-q", "--path", aleo_path, command]) .unwrap_or_else(|error| panic!("`leo {command}` arguments should parse: {error}")); create_session_if_not_set_then(|_| { @@ -1052,11 +1044,11 @@ mod tests { for (arguments, expected_error) in [ ( - ["leo", "-q", "--disable-update-check", "--path", aleo_path, "--package", "member", "upgrade"], + ["leo", "-q", "--path", aleo_path, "--package", "member", "upgrade"], "`--package` cannot be used with an Aleo bytecode file", ), ( - ["leo", "-q", "--disable-update-check", "--path", project_path, "upgrade", "--imports-dir", "imports"], + ["leo", "-q", "--path", project_path, "upgrade", "--imports-dir", "imports"], "`--imports-dir` requires `--path` to point to an Aleo bytecode file", ), ] { @@ -1089,7 +1081,6 @@ mod tests { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::New { command: crate::cli::commands::LeoNew { name: new_pkg.to_string(), library: false, workspace: false }, }, @@ -1131,7 +1122,6 @@ mod tests { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::New { command: crate::cli::commands::LeoNew { name: new_pkg.to_string(), library: false, workspace: false }, }, @@ -1163,7 +1153,6 @@ mod tests { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::Build { command: crate::cli::commands::LeoBuild { options: Default::default(), @@ -1199,7 +1188,6 @@ mod tests { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::Build { command: crate::cli::commands::LeoBuild { options: Default::default(), @@ -1235,7 +1223,6 @@ mod tests { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::Build { command: crate::cli::commands::LeoBuild { options: Default::default(), @@ -1271,7 +1258,6 @@ mod tests { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::Build { command: crate::cli::commands::LeoBuild { options: Default::default(), @@ -1303,7 +1289,6 @@ mod tests { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::Build { command: crate::cli::commands::LeoBuild { options: Default::default(), @@ -1331,7 +1316,6 @@ mod tests { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::Clean { command: crate::cli::commands::LeoClean {} }, path: Some(ws_root.clone()), home: None, @@ -1357,7 +1341,6 @@ mod tests { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::Build { command: crate::cli::commands::LeoBuild { options: Default::default(), @@ -1394,7 +1377,6 @@ mod tests { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::Build { command: crate::cli::commands::LeoBuild { options: Default::default(), @@ -1430,7 +1412,6 @@ mod tests { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::Build { command: crate::cli::commands::LeoBuild { options: Default::default(), @@ -1468,7 +1449,6 @@ mod tests { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::Deploy { command: crate::cli::commands::LeoDeploy { fee_options: Default::default(), @@ -1519,7 +1499,6 @@ mod tests { let imports = ws_root.join("imports"); let deploy = CLI::try_parse_from([ "leo", - "--disable-update-check", "--path", ws_root.to_str().expect("workspace path should be UTF-8"), "deploy", @@ -1548,7 +1527,6 @@ mod tests { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::Deploy { command: crate::cli::commands::LeoDeploy { fee_options: Default::default(), @@ -1602,7 +1580,6 @@ mod tests { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::Deploy { command: crate::cli::commands::LeoDeploy { fee_options: Default::default(), @@ -1657,7 +1634,6 @@ mod tests { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::Deploy { command: crate::cli::commands::LeoDeploy { fee_options: Default::default(), @@ -1715,7 +1691,6 @@ mod tests { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::New { command: crate::cli::commands::LeoNew { name: pkg_name.to_string(), library: false, workspace: false }, }, @@ -1732,7 +1707,6 @@ mod tests { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::Build { command: crate::cli::commands::LeoBuild { options: Default::default(), @@ -2231,7 +2205,6 @@ program app.aleo { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::New { command: LeoNew { name: name.to_string(), library: false, workspace: false } }, path: Some(project_directory.clone()), home: None, @@ -2355,7 +2328,6 @@ function external_nested_function: debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::New { command: LeoNew { name: "grandparent".to_string(), library: false, workspace: false }, }, @@ -2368,7 +2340,6 @@ function external_nested_function: debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::New { command: LeoNew { name: "parent".to_string(), library: false, workspace: false } }, path: Some(parent_directory.clone()), home: None, @@ -2379,7 +2350,6 @@ function external_nested_function: debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::New { command: LeoNew { name: "child".to_string(), library: false, workspace: false } }, path: Some(child_directory.clone()), home: None, @@ -2432,7 +2402,6 @@ program child.aleo { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::Add { command: LeoAdd { name: "parent".to_string(), @@ -2458,7 +2427,6 @@ program child.aleo { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::Add { command: LeoAdd { name: "child".to_string(), @@ -2484,7 +2452,6 @@ program child.aleo { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::Add { command: LeoAdd { name: "child".to_string(), @@ -2539,7 +2506,6 @@ program child.aleo { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::New { command: LeoNew { name: "outer".to_string(), library: false, workspace: false } }, path: Some(outer_directory.clone()), home: None, @@ -2550,7 +2516,6 @@ program child.aleo { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::New { command: LeoNew { name: "inner_1".to_string(), library: false, workspace: false }, }, @@ -2563,7 +2528,6 @@ program child.aleo { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::New { command: LeoNew { name: "inner_2".to_string(), library: false, workspace: false }, }, @@ -2641,7 +2605,6 @@ program inner_2.aleo { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::Add { command: LeoAdd { name: "inner_1".to_string(), @@ -2667,7 +2630,6 @@ program inner_2.aleo { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::Add { command: LeoAdd { name: "inner_2".to_string(), @@ -2721,7 +2683,6 @@ program inner_2.aleo { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::New { command: LeoNew { name: "outer_2".to_string(), library: false, workspace: false }, }, @@ -2734,7 +2695,6 @@ program inner_2.aleo { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::New { command: LeoNew { name: "inner_1".to_string(), library: false, workspace: false }, }, @@ -2747,7 +2707,6 @@ program inner_2.aleo { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::New { command: LeoNew { name: "inner_2".to_string(), library: false, workspace: false }, }, @@ -2859,7 +2818,6 @@ program inner_2.aleo { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::Add { command: LeoAdd { name: "inner_1".to_string(), @@ -2885,7 +2843,6 @@ program inner_2.aleo { debug: false, quiet: false, json_output: None, - disable_update_check: false, command: Commands::Add { command: LeoAdd { name: "inner_2".to_string(), diff --git a/documentation/cli/add.md b/documentation/cli/add.md index 1a515cd3412..5853f9707ae 100644 --- a/documentation/cli/add.md +++ b/documentation/cli/add.md @@ -26,7 +26,7 @@ To add a program already deployed onchain as a dependency to your project, run t leo add --network ``` -`` is the name of the imported program. Leo makes sure that the program exists before it changes `program.json`. +`` is the name of the imported program. Leo makes sure that the program exists before it changes `program.json`. It records the resolved edition and checksum in `leo.lock`. Later builds reuse the locked edition. Use [`leo update`](./update.md) to refresh dependencies without changing the manifest. The built-in `credits.aleo` program needs no network lock entry. To add another member of the enclosing workspace as a dependency: @@ -90,11 +90,7 @@ Pin a git dependency to a specific branch, tag, or revision. These require `--gi ### `-e ` -Specifies the expected edition of the program being imported. Only passing this flag will assume that the program is being imported from the network. - -:::warning -Do not use this feature unless you know what you are doing! -::: +Set an exact network edition in `program.json`. This flag selects a network dependency without `--network`. Builds and `leo update` keep this edition fixed. If you omit the edition, Leo records the current edition in `leo.lock`; a later `leo update` can select a newer edition. ### `--dev` diff --git a/documentation/cli/build.md b/documentation/cli/build.md index 8bdbf05ac19..1c0c572a041 100644 --- a/documentation/cli/build.md +++ b/documentation/cli/build.md @@ -30,6 +30,10 @@ The output has one import line for each imported program. Leo does not show this The build also generates an **ABI file** at `build/{PROGRAM_NAME}/abi.json` describing your program's public interface (transitions, mappings, and types). See the [ABI Generation guide](../guides/abi.md) for details on the format and type lowering specification. +## Dependency Locks + +The first build records resolved network editions and checksums in `leo.lock`. Builds reuse locked network editions and Git commits. Keep this file under version control. Use [`leo update`](./update.md) to refresh dependencies within the manifest requirements. + ## Checksums The [`std::prog::function_checksum`](../language/standard_library.md#stdprog) function returns program, entry, and view function checksums. Use these checksums in a [constructor](../language/structure.md#constructor) that pins functions across upgrades. To print the checksums, pass `--checksums`: @@ -55,7 +59,7 @@ Each checksum is the 32-byte SHA3-256 hash of the component's Aleo source. The [ Print the program checksum and the checksum of each entry and view function (the `std::prog::function_checksum` targets). --no-cache - Don't use the dependency cache. + Download network dependencies again and verify locked checksums. Keep locked editions. --no-local Don't use the local source code. --network diff --git a/documentation/cli/deploy.md b/documentation/cli/deploy.md index d5ab92d5ae4..16eeb6f1cff 100644 --- a/documentation/cli/deploy.md +++ b/documentation/cli/deploy.md @@ -277,7 +277,7 @@ Options: --build-tests Build tests along with the main program and dependencies. --no-cache - Don't use the dependency cache. + Download network dependencies again and verify locked checksums. Keep locked editions. --no-local Don't use the local source code. ``` diff --git a/documentation/cli/execute.md b/documentation/cli/execute.md index 0d753d4c9ff..0a26d8cab7b 100644 --- a/documentation/cli/execute.md +++ b/documentation/cli/execute.md @@ -203,7 +203,7 @@ Options: --build-tests Build tests along with the main program and dependencies. --no-cache - Don't use the dependency cache. + Download network dependencies again and verify locked checksums. Keep locked editions. --no-local Don't use the local source code. ``` diff --git a/documentation/cli/overview.md b/documentation/cli/overview.md index d0e771a5d18..5958506db50 100644 --- a/documentation/cli/overview.md +++ b/documentation/cli/overview.md @@ -39,7 +39,7 @@ You can print the list of commands by running `leo --help` - [`remove`](./remove.md) - Remove a dependency from the current project. - [`run`](./run.md) - Run a program without producing a proof. - [`test`](./test.md) - Run the test cases for a Leo project. -- [`update`](./update.md) - Update to the latest version of Leo. +- [`update`](./update.md) - Update dependencies in `leo.lock` within the manifest requirements. - [`upgrade`](./upgrade.md) - Upgrade a deployed program on the Aleo network. - [`synthesize`](./synthesize.md) - Generate proving and verifying keys for a program. - [`fmt`](./fmt.md) - Format Leo source files. *(plugin)* @@ -103,4 +103,4 @@ leo build --json-output ### `-p ` -Targets a specific [workspace](../guides/workspaces.md) member by name. The name can be the member directory, program name, or program name without the `.aleo` suffix. For example, use `token.aleo`. Use this option only in a workspace. If `workspace.json` does not exist, the command reports an error. +Targets a specific [workspace](../guides/workspaces.md) member by name. The name can be the member directory, program name, or program name without the `.aleo` suffix. For example, use `token.aleo`. `leo update` does not accept this option; use `leo update NAME` to select a dependency. Use this option only in a workspace. If `workspace.json` does not exist, the command reports an error. diff --git a/documentation/cli/run.md b/documentation/cli/run.md index 376bed31668..ea249c86f15 100644 --- a/documentation/cli/run.md +++ b/documentation/cli/run.md @@ -56,7 +56,7 @@ leo run -- -- ... --build-tests Build tests along with the main program and dependencies. --no-cache - Don't use the dependency cache. + Download network dependencies again and verify locked checksums. Keep locked editions. --no-local Don't use the local source code. --private-key diff --git a/documentation/cli/test.md b/documentation/cli/test.md index cdd149ac806..e95014d986b 100644 --- a/documentation/cli/test.md +++ b/documentation/cli/test.md @@ -37,7 +37,7 @@ Check out the [**Testing**](./../guides/testing.md) guide for more information. --prove Generate a full ZK proof for each executed transaction. Proof generation is disabled by default to keep test runs fast. --no-cache - Don't use the dependency cache. + Download network dependencies again and verify locked checksums. Keep locked editions. --no-local Don't use the local source code. --offline diff --git a/documentation/cli/update.md b/documentation/cli/update.md index 6d96337173e..693325138ca 100644 --- a/documentation/cli/update.md +++ b/documentation/cli/update.md @@ -34,7 +34,7 @@ Leo fetches and validates the selected dependencies before it writes the lock. N The first checksum comes from the configured endpoint. It detects later changes to that edition, but it does not prove that the first response was correct. -Normal builds use the locked versions. `leo build --no-cache` downloads those versions again and checks their checksums; it does not update them. +Normal builds use the locked versions. `leo build --no-cache` downloads locked network editions again and checks their checksums; it does not update them. ## Arguments and flags @@ -56,4 +56,4 @@ Select the endpoint used to resolve and download network dependencies. ## Update the Leo installation -`leo update` updates dependency locks. Leo does not check for new releases or show release notices. To install the latest Leo release and plugins, follow the [installation instructions](https://github.com/ProvableHQ/leo#-build-guide). +`leo update` updates dependency locks. Leo does not check for new releases or show release notices. The `--disable-update-check` flag was removed; remove it from existing scripts. To install the latest Leo release and plugins, follow the [installation instructions](https://github.com/ProvableHQ/leo#-build-guide). diff --git a/documentation/cli/upgrade.md b/documentation/cli/upgrade.md index 0e0ceb0cdf5..f22583d9652 100644 --- a/documentation/cli/upgrade.md +++ b/documentation/cli/upgrade.md @@ -10,7 +10,7 @@ toc_max_heading_level: 2 # `leo upgrade` -Upgrades a program that is already deployed on the network. +Upgrades a program that is already deployed on the network. Leo checks the current deployed edition of the program being upgraded, even if an older dependency lock exists for that program. Other network dependencies use their locked editions and checksums. See the **[Upgrading Programs](./../guides/program_upgradability.md)** guide for more details. @@ -121,7 +121,7 @@ Options: --build-tests Build tests along with the main program and dependencies. --no-cache - Don't use the dependency cache. + Download network dependencies again and verify locked checksums. Keep locked editions. --no-local Don't use the local source code. ``` diff --git a/documentation/code_snippets/dependencies/git_dep/leo.lock b/documentation/code_snippets/dependencies/git_dep/leo.lock index 86e334c3e53..52629f2f8c4 100644 --- a/documentation/code_snippets/dependencies/git_dep/leo.lock +++ b/documentation/code_snippets/dependencies/git_dep/leo.lock @@ -1,5 +1,5 @@ { - "version": 1, + "version": 2, "git": [ { "name": "helloworld.aleo", diff --git a/documentation/getting_started/installation.md b/documentation/getting_started/installation.md index b2ec2385a01..c7a12db77f1 100644 --- a/documentation/getting_started/installation.md +++ b/documentation/getting_started/installation.md @@ -150,6 +150,16 @@ leo +## Updating Leo and Plugins + +Update Leo and its plugins with the installation method you used above. For a Cargo installation, run: + +```bash +cargo install leo-lang leo-fmt leo-lsp +``` + +Leo does not check for new releases or show release notices. [`leo update`](../cli/update.md) updates project dependencies in `leo.lock`; it does not install Leo or its plugins. + --- For distribution maintainers and detailed artifact information, see the [Binary Distribution Reference](../guides/binary_distribution.md). diff --git a/documentation/guides/dependencies.md b/documentation/guides/dependencies.md index 8b66b22c5e4..782565b999f 100644 --- a/documentation/guides/dependencies.md +++ b/documentation/guides/dependencies.md @@ -45,6 +45,8 @@ NETWORK=mainnet leo add credits --network You can also set `NETWORK=mainnet` in `.env`. If you do not use `--endpoint`, Leo uses `ENDPOINT` from the environment. Leo makes sure that the program exists on the selected network before it changes `program.json`. Use `--network-retries` to set the number of retries. +Leo records the resolved network edition and checksum in `leo.lock`. Later builds reuse that edition. The built-in `credits.aleo` program comes from snarkVM and needs no network lock entry. + This adds an entry to your `program.json`: ```json @@ -57,8 +59,8 @@ This adds an entry to your `program.json`: { "name": "credits.aleo", "location": "network", - "network": "testnet", - "path": null + "path": null, + "edition": null } ] } @@ -80,8 +82,8 @@ This records the path in `program.json`: { "name": "my_library.aleo", "location": "local", - "network": null, - "path": "./path/to/my_library" + "path": "./path/to/my_library", + "edition": null } ] } @@ -152,11 +154,11 @@ When Leo first resolves a git dependency, it writes a `leo.lock` file. This file ```json file=../code_snippets/dependencies/git_dep/leo.lock title="leo.lock" ``` -Subsequent builds reuse the locked commit, so builds are reproducible. Commit `leo.lock` to version control to share exact dependency versions. A change to `branch`, `tag`, or `rev` in `program.json` makes Leo resolve the dependency again and update the lock. The reference kind determines if a rebuild uses the network. A `tag` or `rev` uses the cache without network access. A `branch`, including the default branch, uses the remote during each build that has network access. +Builds reuse the locked commit for every Git reference, including branches. If the cached checkout is missing, Leo downloads the repository and restores that commit. Commit `leo.lock` to version control to share exact dependency versions. -A `tag` or `rev` is immutable. After the lock operation, Leo reuses it from the cache. A `branch`, including the default branch, is mutable. During each build with network access, Leo resolves the latest branch commit and updates the lock. Thus, builds at different times can use different commits. Pin a `tag` or `rev` when you need a fixed dependency. +Use `leo update NAME` to update a branch dependency, or `leo update` to update all eligible dependencies. Packages from the same Git repository and reference update together. Locked tags and revisions stay fixed. A change to `branch`, `tag`, or `rev` in `program.json` makes Leo resolve the new reference. -Pass `--offline` to `leo build` to skip all git fetching and build from the locked commits and the local cache, even for branch references. `leo remove` deletes the removed dependency's entries from `leo.lock`. +Pass `--offline` to `leo build` to use only locked Git commits with cached checkouts. For Git dependencies, `leo remove` also removes the corresponding lock entries. ## `dependencies` vs. `dev_dependencies` @@ -171,12 +173,14 @@ Tests can already use `dependencies`. Thus, put a library that `src` and tests u This reference applies to each dependency kind, not only workspace members. `leo add` completes these fields. If you edit `program.json` manually, Leo validates each dependency when it loads the manifest. Leo rejects incompatible field combinations. Each entry has a `location`, and the other fields depend on it: -| `location` | `path` | `edition` | `network` | -| ----------- | ----------- | ----------- | -------------------- | -| `network` | not allowed | optional | target network | -| `local` | required | not allowed | — | -| `workspace` | not allowed | not allowed | — | -| `git` | not allowed | not allowed | — | +| `location` | `path` | `edition` | +| ----------- | ----------- | ----------- | +| `network` | not allowed | optional | +| `local` | required | not allowed | +| `workspace` | not allowed | not allowed | +| `git` | not allowed | not allowed | + +The manifest does not select a network. Commands use their network option or the `NETWORK` environment variable. `leo add --network` selects the dependency source; use `NETWORK` to select its verification network. Network lock entries are stored separately for each network. The same rules apply to entries in `dev_dependencies`. `workspace` entries are looked up in `workspace.json` and resolved to a local path automatically. `git` entries additionally take a `git` object with a `url` and at most one of `branch`/`tag`/`rev`. @@ -201,7 +205,7 @@ When you run a Leo command, dependencies are resolved as follows: 2. **For each dependency:** - **Workspace**: Look up the member in `workspace.json` and resolve to a local path - **Local**: Read the Leo source from the specified path and compile it, or use Aleo Instructions file - - **Network**: Fetch the bytecode from the Aleo network (or cache) + - **Network**: Use the locked edition and check its checksum against cached or downloaded bytecode. If no lock entry exists, resolve the manifest edition or the latest edition and record its checksum. - **Git**: Reuse the commit pinned in `leo.lock` if present. Otherwise clone the repository, resolve the reference to a commit, and check it out. The checked-out package is then treated exactly like a local one. 3. **Resolve transitive dependencies** - if your dependency imports other programs, those are fetched too 4. **Topologically sort** all programs so dependencies are processed before dependents @@ -223,7 +227,7 @@ Different commands handle caching differently: | `leo upgrade` | Always fetches fresh | | `leo synthesize` | Always fetches fresh | -Commands that generate proofs (`execute`, `deploy`, `upgrade`, `synthesize`) always fetch fresh bytecode because proofs include commitments to the exact bytecode of dependencies. Using stale cached bytecode would produce invalid proofs if a dependency has been upgraded on-chain. +`leo execute`, `leo deploy`, `leo upgrade`, and `leo synthesize` download network dependency bytecode again. Project dependencies still use their locked editions and checksums. A fresh download does not update the lock to a newer edition. Remote commands outside a project can resolve programs without a supplied lock. To force a fresh fetch during `build` or `run`: @@ -231,6 +235,8 @@ To force a fresh fetch during `build` or `run`: leo build --no-cache ``` +This downloads locked network editions again and verifies their checksums. Use `leo update` to select newer editions. The first download trusts the configured endpoint; a checksum detects later changes but does not authenticate the first response. + ## Program Editions An **edition** is the version number of a deployed program on the Aleo network: @@ -240,7 +246,7 @@ An **edition** is the version number of a deployed program on the Aleo network: - **Edition 2:** Second upgrade - ...and so on -By default, Leo fetches the **latest** edition of a network dependency. To pin to a specific edition: +When no manifest edition or lock entry exists, Leo fetches the latest edition and records it in `leo.lock`. Later builds keep that edition, even if a newer one is deployed. To require an exact edition in the manifest: ```bash leo add some_program.aleo --edition 3 @@ -252,17 +258,22 @@ This records the pinned edition in the manifest: { "name": "some_program.aleo", "location": "network", - "network": "testnet", "path": null, "edition": 3 } ``` -**When to pin editions:** +The lock provides reproducible dependency resolution without an exact manifest edition. Set an exact edition when you also want `leo update` to keep it fixed. To change an exact requirement, change the manifest or use `leo add NAME --edition EDITION`. + +To update dependencies whose manifest requirements permit a newer version: + +```bash +leo update --dry-run +leo update +leo update some_program.aleo +``` -- When you need reproducible builds -- When a dependency upgrade would break your program -- When you want to avoid unexpected behavior changes +Updates leave `program.json` unchanged. In a workspace, they resolve all members and development dependencies against the shared lock. A named update preserves unrelated versions where possible. If resolution fails, the existing lock stays unchanged. See [`leo update`](../cli/update.md) for options. **Note:** Local dependencies do not have editions - they are always compiled from your current source code. @@ -280,4 +291,4 @@ This deploys all local dependencies in topological order, then deploys your main ### Network Dependencies at Deploy Time -During deployment, Leo gets current bytecode for all network dependencies. This operation makes the deployment transaction reference the current on-chain editions. Since the V8 consensus upgrade, an edition 0 network dependency must have a constructor. If the constructor does not exist, Leo reports that you must first upgrade the dependency on-chain. +During deployment, Leo downloads the locked network dependency editions and verifies their checksums. Missing lock entries are resolved and recorded. Use `leo update` when you want to select newer editions that the manifest permits. Since the V8 consensus upgrade, an edition 0 network dependency must have a constructor. If the constructor does not exist, Leo reports that you must first upgrade the dependency on-chain. diff --git a/documentation/guides/deploying.md b/documentation/guides/deploying.md index 34af403c9a8..d8898086926 100644 --- a/documentation/guides/deploying.md +++ b/documentation/guides/deploying.md @@ -66,7 +66,7 @@ Options: --build-tests Build tests along with the main program and dependencies. --no-cache - Don't use the dependency cache. + Download network dependencies again and verify locked checksums. Keep locked editions. --no-local Don't use the local source code. -h, --help diff --git a/documentation/guides/workspaces.md b/documentation/guides/workspaces.md index 6d9f1ede9e4..027e136e8d2 100644 --- a/documentation/guides/workspaces.md +++ b/documentation/guides/workspaces.md @@ -93,6 +93,10 @@ my_project/ Build artifacts are in one `build/` directory at the workspace root. The compilation unit name identifies each artifact. After Leo builds a unit as a member or dependency, other members reuse it. Run `leo clean` from any workspace directory to remove the shared `build/` directory. +## Dependency Locks + +Members share `leo.lock` at the workspace root. Run `leo update` from the root or a member to update eligible dependencies across all members, including development dependencies. Use `leo update NAME` to select one dependency, or `--dry-run` to preview changes. Exact manifest editions remain fixed. This command does not accept `--package`. + ## Member Dependencies Members can depend on each other using [workspace dependencies](./dependencies.md#workspace-dependencies). For example, if `swap` depends on `token`, add the dependency with: diff --git a/documentation/language/layout.md b/documentation/language/layout.md index 3b2277ea5e2..494c29e5465 100644 --- a/documentation/language/layout.md +++ b/documentation/language/layout.md @@ -19,7 +19,7 @@ The program ID in `program` is the official name that other developers will be a "program": "hello.aleo", ``` -Dependencies will be added to the field of the same name, as they are added. The dependencies are also pegged in the **leo.lock** file. +The `dependencies` field records dependency requirements. `leo.lock` records resolved Git commits and network editions with checksums. Builds reuse those versions. Use [`leo update`](../cli/update.md) to refresh them within the manifest requirements. The `src/` directory is where all of your Leo code will live. The main entry point of your project is a file in this directory appropriately named `main.leo`. Calls to many of the Leo CLI commands will require you to have this file within your project in order to succeed properly. diff --git a/tests/expectations/cli/aleo_dep_imports_local_dep/COMMANDS b/tests/expectations/cli/aleo_dep_imports_local_dep/COMMANDS index 3ba5b05025b..f4c781171ae 100755 --- a/tests/expectations/cli/aleo_dep_imports_local_dep/COMMANDS +++ b/tests/expectations/cli/aleo_dep_imports_local_dep/COMMANDS @@ -2,4 +2,4 @@ LEO_BIN=${1} -${LEO_BIN} --disable-update-check build +${LEO_BIN} build diff --git a/tests/expectations/cli/aleo_dep_reversed_order/COMMANDS b/tests/expectations/cli/aleo_dep_reversed_order/COMMANDS index 3ba5b05025b..f4c781171ae 100755 --- a/tests/expectations/cli/aleo_dep_reversed_order/COMMANDS +++ b/tests/expectations/cli/aleo_dep_reversed_order/COMMANDS @@ -2,4 +2,4 @@ LEO_BIN=${1} -${LEO_BIN} --disable-update-check build +${LEO_BIN} build diff --git a/tests/expectations/cli/broadcast_error/COMMANDS b/tests/expectations/cli/broadcast_error/COMMANDS index 37f25af16e3..6979dea26f4 100755 --- a/tests/expectations/cli/broadcast_error/COMMANDS +++ b/tests/expectations/cli/broadcast_error/COMMANDS @@ -6,7 +6,7 @@ LEO_BIN=${1} PRIVATE_KEY="APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH" -COMMON_OPTS="--disable-update-check --broadcast --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" +COMMON_OPTS="--broadcast --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" # First deploy should succeed $LEO_BIN deploy -y $COMMON_OPTS diff --git a/tests/expectations/cli/build_checksums/COMMANDS b/tests/expectations/cli/build_checksums/COMMANDS index 069af46e2f7..d9177af022c 100755 --- a/tests/expectations/cli/build_checksums/COMMANDS +++ b/tests/expectations/cli/build_checksums/COMMANDS @@ -7,14 +7,14 @@ trap 'rm -rf "${TMP_DIR}"' EXIT # `--checksums` prints the program checksum and the checksum of each entry and view function # (the `std::prog::function_checksum` targets) to stdout. -${LEO_BIN} --disable-update-check build --checksums >"${TMP_DIR}/stdout" 2>"${TMP_DIR}/stderr" +${LEO_BIN} build --checksums >"${TMP_DIR}/stdout" 2>"${TMP_DIR}/stderr" grep -qF "The program checksum is:" "${TMP_DIR}/stdout" grep -qF '`main` function checksum is:' "${TMP_DIR}/stdout" grep -qF '`peek` function checksum is:' "${TMP_DIR}/stdout" # Without the flag, no checksum lines are printed. -${LEO_BIN} --disable-update-check build >"${TMP_DIR}/stdout2" 2>"${TMP_DIR}/stderr2" +${LEO_BIN} build >"${TMP_DIR}/stdout2" 2>"${TMP_DIR}/stderr2" if grep -qF "checksum is:" "${TMP_DIR}/stdout2"; then echo "checksums printed without the --checksums flag" exit 1 diff --git a/tests/expectations/cli/build_missing_constructor/COMMANDS b/tests/expectations/cli/build_missing_constructor/COMMANDS index 19e76ae73d1..8ced5eaed1f 100755 --- a/tests/expectations/cli/build_missing_constructor/COMMANDS +++ b/tests/expectations/cli/build_missing_constructor/COMMANDS @@ -5,7 +5,7 @@ LEO_BIN=${1} TMP_DIR=$(mktemp -d) trap 'rm -rf "${TMP_DIR}"' EXIT -if ${LEO_BIN} --disable-update-check build >"${TMP_DIR}/stdout" 2>"${TMP_DIR}/stderr"; then +if ${LEO_BIN} build >"${TMP_DIR}/stdout" 2>"${TMP_DIR}/stderr"; then echo "leo build unexpectedly succeeded" exit 1 fi diff --git a/tests/expectations/cli/leo_devnode_missing_private_key/COMMANDS b/tests/expectations/cli/leo_devnode_missing_private_key/COMMANDS index d2893cd34ca..ad49b2ae2f0 100755 --- a/tests/expectations/cli/leo_devnode_missing_private_key/COMMANDS +++ b/tests/expectations/cli/leo_devnode_missing_private_key/COMMANDS @@ -2,4 +2,4 @@ LEO_BIN=${1} -${LEO_BIN} --disable-update-check devnode start +${LEO_BIN} devnode start diff --git a/tests/expectations/cli/local_aleo_dependency/COMMANDS b/tests/expectations/cli/local_aleo_dependency/COMMANDS index 3ba5b05025b..f4c781171ae 100755 --- a/tests/expectations/cli/local_aleo_dependency/COMMANDS +++ b/tests/expectations/cli/local_aleo_dependency/COMMANDS @@ -2,4 +2,4 @@ LEO_BIN=${1} -${LEO_BIN} --disable-update-check build +${LEO_BIN} build diff --git a/tests/expectations/cli/multiple_leo_deps/COMMANDS b/tests/expectations/cli/multiple_leo_deps/COMMANDS index 854f1aa895d..47525daf034 100755 --- a/tests/expectations/cli/multiple_leo_deps/COMMANDS +++ b/tests/expectations/cli/multiple_leo_deps/COMMANDS @@ -3,4 +3,4 @@ LEO_BIN=${1} cd parent || exit 1 -$LEO_BIN --disable-update-check build +$LEO_BIN build diff --git a/tests/expectations/cli/network_dependency/COMMANDS b/tests/expectations/cli/network_dependency/COMMANDS index a5715d7e6a3..b655111115b 100755 --- a/tests/expectations/cli/network_dependency/COMMANDS +++ b/tests/expectations/cli/network_dependency/COMMANDS @@ -5,7 +5,7 @@ set -e LEO_BIN=${1} PRIVATE_KEY="APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH" -COMMON_OPTS="-y --disable-update-check --broadcast --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" +COMMON_OPTS="-y --broadcast --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" cd test_program1 || exit 1 echo "DEPLOYMENT 1" diff --git a/tests/expectations/cli/program_name_mismatch/COMMANDS b/tests/expectations/cli/program_name_mismatch/COMMANDS index 3ba5b05025b..f4c781171ae 100755 --- a/tests/expectations/cli/program_name_mismatch/COMMANDS +++ b/tests/expectations/cli/program_name_mismatch/COMMANDS @@ -2,4 +2,4 @@ LEO_BIN=${1} -${LEO_BIN} --disable-update-check build +${LEO_BIN} build diff --git a/tests/expectations/cli/test_abi_compat/COMMANDS b/tests/expectations/cli/test_abi_compat/COMMANDS index 54c2887fdfe..bd524cc101b 100755 --- a/tests/expectations/cli/test_abi_compat/COMMANDS +++ b/tests/expectations/cli/test_abi_compat/COMMANDS @@ -10,40 +10,40 @@ run() { } # The standard is a JSON ABI; generate it from standard.aleo once up front. -${LEO_BIN} --disable-update-check abi standard.aleo -o std_abi >/dev/null +${LEO_BIN} abi standard.aleo -o std_abi >/dev/null STD=std_abi/standard.aleo.abi.json echo "=== satisfies: superset vs standard ===" -run ${LEO_BIN} --disable-update-check abi compatible.aleo --satisfies ${STD} +run ${LEO_BIN} abi compatible.aleo --satisfies ${STD} echo "" echo "=== satisfies: identical (standard vs standard) ===" -run ${LEO_BIN} --disable-update-check abi standard.aleo --satisfies ${STD} +run ${LEO_BIN} abi standard.aleo --satisfies ${STD} echo "" echo "=== does not satisfy: missing function, mapping, and struct ===" -run ${LEO_BIN} --disable-update-check abi missing.aleo --satisfies ${STD} +run ${LEO_BIN} abi missing.aleo --satisfies ${STD} echo "" echo "=== does not satisfy: differing function, record, and struct definitions ===" -run ${LEO_BIN} --disable-update-check abi differs.aleo --satisfies ${STD} +run ${LEO_BIN} abi differs.aleo --satisfies ${STD} echo "" echo "=== JSON report via --output (satisfied) ===" -run ${LEO_BIN} --disable-update-check abi compatible.aleo --satisfies ${STD} --output report_ok.json +run ${LEO_BIN} abi compatible.aleo --satisfies ${STD} --output report_ok.json cat report_ok.json echo "" echo "" echo "=== JSON report via --output (not satisfied) ===" -run ${LEO_BIN} --disable-update-check abi missing.aleo --satisfies ${STD} --output report_bad.json +run ${LEO_BIN} abi missing.aleo --satisfies ${STD} --output report_bad.json cat report_bad.json echo "" echo "" echo "=== error: --satisfies file does not exist ===" -run ${LEO_BIN} --disable-update-check abi compatible.aleo --satisfies does_not_exist.abi.json +run ${LEO_BIN} abi compatible.aleo --satisfies does_not_exist.abi.json echo "" echo "=== error: --satisfies given a .aleo file (only .abi.json is accepted) ===" -run ${LEO_BIN} --disable-update-check abi compatible.aleo --satisfies standard.aleo +run ${LEO_BIN} abi compatible.aleo --satisfies standard.aleo diff --git a/tests/expectations/cli/test_abi_comprehensive/COMMANDS b/tests/expectations/cli/test_abi_comprehensive/COMMANDS index 53fdf9f28f1..12928070ada 100755 --- a/tests/expectations/cli/test_abi_comprehensive/COMMANDS +++ b/tests/expectations/cli/test_abi_comprehensive/COMMANDS @@ -2,7 +2,7 @@ LEO_BIN=${1} -${LEO_BIN} --disable-update-check build --checksums +${LEO_BIN} build --checksums echo "" echo "=== build/abi_test/abi.json ===" diff --git a/tests/expectations/cli/test_abi_from_aleo/COMMANDS b/tests/expectations/cli/test_abi_from_aleo/COMMANDS index d8996b11c80..436c8b85389 100755 --- a/tests/expectations/cli/test_abi_from_aleo/COMMANDS +++ b/tests/expectations/cli/test_abi_from_aleo/COMMANDS @@ -3,28 +3,28 @@ LEO_BIN=${1} echo "=== simple.aleo ===" -${LEO_BIN} --disable-update-check abi simple.aleo +${LEO_BIN} abi simple.aleo echo "" echo "=== simple.aleo with --output ===" -${LEO_BIN} --disable-update-check abi simple.aleo -o simple_out +${LEO_BIN} abi simple.aleo -o simple_out cat simple_out/simple.aleo.abi.json echo "" echo "=== comprehensive.aleo ===" -${LEO_BIN} --disable-update-check abi comprehensive.aleo +${LEO_BIN} abi comprehensive.aleo echo "" echo "=== importer.aleo (auto-detected sibling imports/) ===" -${LEO_BIN} --disable-update-check abi importer.aleo +${LEO_BIN} abi importer.aleo echo "" echo "=== importer.aleo with explicit --imports-dir ===" -${LEO_BIN} --disable-update-check abi importer.aleo --imports-dir imports +${LEO_BIN} abi importer.aleo --imports-dir imports echo "" echo "=== importer.aleo with --output (bundle layout) ===" -${LEO_BIN} --disable-update-check abi importer.aleo -o importer_out +${LEO_BIN} abi importer.aleo -o importer_out echo "--- importer_out/importer.aleo.abi.json ---" cat importer_out/importer.aleo.abi.json echo "" diff --git a/tests/expectations/cli/test_add/COMMANDS b/tests/expectations/cli/test_add/COMMANDS index 64b51579195..b992e108e77 100755 --- a/tests/expectations/cli/test_add/COMMANDS +++ b/tests/expectations/cli/test_add/COMMANDS @@ -2,10 +2,10 @@ LEO_BIN=${1} -${LEO_BIN} --disable-update-check add --network credits.aleo --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} +${LEO_BIN} add --network credits.aleo --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} -${LEO_BIN} --disable-update-check build +${LEO_BIN} build -${LEO_BIN} --disable-update-check execute main 1u32 2u32 --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH --yes --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 +${LEO_BIN} execute main 1u32 2u32 --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH --yes --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 -${LEO_BIN} --disable-update-check execute main 1u32 2u32 --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH --yes --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 +${LEO_BIN} execute main 1u32 2u32 --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH --yes --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 diff --git a/tests/expectations/cli/test_add_dependency/COMMANDS b/tests/expectations/cli/test_add_dependency/COMMANDS index 4103de1f9c5..9382d3034a0 100755 --- a/tests/expectations/cli/test_add_dependency/COMMANDS +++ b/tests/expectations/cli/test_add_dependency/COMMANDS @@ -5,48 +5,48 @@ LEO_BIN=${1} cd my_program || exit 1 echo "--- 1. leo add with no source flag (should fail) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add some_dep +${LEO_BIN} add some_dep echo "no source exit code: $?" cat program.json echo "--- 2. leo add with multiple flags (should fail) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add some_dep --network --local ../some_path +${LEO_BIN} add some_dep --network --local ../some_path echo "multiple flags exit code: $?" cat program.json echo "--- 3. leo add --network (should succeed) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add --network credits --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} +${LEO_BIN} add --network credits --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} cat program.json echo "--- 4. leo add --local (should succeed) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add some_local --local ../some_local +${LEO_BIN} add some_local --local ../some_local cat program.json echo "--- 5. leo add --local (should succeed) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add my_lib --local ../my_lib +${LEO_BIN} add my_lib --local ../my_lib cat program.json echo "--- 6. leo add overwrite existing dep (should succeed) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add credits --local ../credits +${LEO_BIN} add credits --local ../credits cat program.json echo "--- 7. leo add --dev for a program not on the network (should fail) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add --dev --network dev_dep --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} +${LEO_BIN} add --dev --network dev_dep --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} echo "missing network dep exit code: $?" cat program.json echo "--- 8. leo add --dev for a program on the network (should succeed) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add --dev --network credits --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} +${LEO_BIN} add --dev --network credits --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} echo "network dev dep exit code: $?" cat program.json echo "--- 9. leo remove library dep by bare name (should succeed) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check remove my_lib +${LEO_BIN} remove my_lib cat program.json echo "--- 10. leo remove program dep by bare name (should succeed) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check remove some_local +${LEO_BIN} remove some_local cat program.json echo "--- 11. leo remove non-existent dep (should fail) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check remove nonexistent || true +${LEO_BIN} remove nonexistent || true diff --git a/tests/expectations/cli/test_add_library_to_program/COMMANDS b/tests/expectations/cli/test_add_library_to_program/COMMANDS index 1dc9e2af89d..4ae33d59abe 100755 --- a/tests/expectations/cli/test_add_library_to_program/COMMANDS +++ b/tests/expectations/cli/test_add_library_to_program/COMMANDS @@ -6,9 +6,9 @@ cd my_program || exit 1 echo "--- leo add actual_lib.aleo --local (should fail: dep is a library, not a program) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add actual_lib.aleo --local ../actual_lib +${LEO_BIN} add actual_lib.aleo --local ../actual_lib echo "add exit code: $?" echo "--- leo add actual_lib --local (should succeed: auto-detected as library) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add actual_lib --local ../actual_lib +${LEO_BIN} add actual_lib --local ../actual_lib diff --git a/tests/expectations/cli/test_add_workspace/COMMANDS b/tests/expectations/cli/test_add_workspace/COMMANDS index 6505df42a91..ff7648d30ef 100755 --- a/tests/expectations/cli/test_add_workspace/COMMANDS +++ b/tests/expectations/cli/test_add_workspace/COMMANDS @@ -5,15 +5,15 @@ LEO_BIN=${1} cd swap || exit 1 echo "--- 1. leo add --workspace token (should succeed) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add --workspace token +${LEO_BIN} add --workspace token cat program.json echo "--- 2. leo add --workspace --dev token (should succeed) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add --workspace --dev token +${LEO_BIN} add --workspace --dev token cat program.json echo "--- 3. leo add --workspace nonexistent (should fail) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add --workspace nonexistent || true +${LEO_BIN} add --workspace nonexistent || true echo "exit code: $?" echo "--- 4. leo add --workspace outside workspace (should fail) ---" | tee /dev/stderr @@ -37,6 +37,6 @@ program standalone.aleo { } SRC cd "${STANDALONE_DIR}" || exit 1 -${LEO_BIN} --disable-update-check add --workspace token || true +${LEO_BIN} add --workspace token || true echo "exit code: $?" rm -rf "${STANDALONE_DIR}" diff --git a/tests/expectations/cli/test_ambiguous_package/COMMANDS b/tests/expectations/cli/test_ambiguous_package/COMMANDS index c13a38983fb..aec7a542afc 100755 --- a/tests/expectations/cli/test_ambiguous_package/COMMANDS +++ b/tests/expectations/cli/test_ambiguous_package/COMMANDS @@ -6,5 +6,5 @@ cd ambiguous_pkg || exit 1 echo "--- leo build (should fail: both main.leo and lib.leo exist) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check build +${LEO_BIN} build echo "build exit code: $?" diff --git a/tests/expectations/cli/test_command_shortcuts/COMMANDS b/tests/expectations/cli/test_command_shortcuts/COMMANDS index 08c6d9cd66f..29928eccd9e 100755 --- a/tests/expectations/cli/test_command_shortcuts/COMMANDS +++ b/tests/expectations/cli/test_command_shortcuts/COMMANDS @@ -2,5 +2,5 @@ LEO_BIN=${1} -${LEO_BIN} --disable-update-check b -${LEO_BIN} --disable-update-check r main 1u32 2u32 +${LEO_BIN} b +${LEO_BIN} r main 1u32 2u32 diff --git a/tests/expectations/cli/test_dependency_visible_to_tests/COMMANDS b/tests/expectations/cli/test_dependency_visible_to_tests/COMMANDS index 45e52d48d38..1212c64daea 100755 --- a/tests/expectations/cli/test_dependency_visible_to_tests/COMMANDS +++ b/tests/expectations/cli/test_dependency_visible_to_tests/COMMANDS @@ -8,4 +8,4 @@ LEO_BIN=${1} cd my_app || exit 1 echo "--- leo test with a dependencies-only library named from a test ---" -${LEO_BIN} --disable-update-check test +${LEO_BIN} test diff --git a/tests/expectations/cli/test_deploy/COMMANDS b/tests/expectations/cli/test_deploy/COMMANDS index d7d5fe8cf54..56d8c26ffc2 100755 --- a/tests/expectations/cli/test_deploy/COMMANDS +++ b/tests/expectations/cli/test_deploy/COMMANDS @@ -7,10 +7,10 @@ LEO_BIN=${1} REMOTE=$(mktemp -d) trap 'rm -rf "$REMOTE"' EXIT PRIVATE_KEY="APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH" -COMMON_OPTS="--disable-update-check --broadcast --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" +COMMON_OPTS="--broadcast --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" ALEO_FILE="./build/some_sample_leo_program/some_sample_leo_program.aleo" -$LEO_BIN build --disable-update-check --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} +$LEO_BIN build --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} $LEO_BIN --path $ALEO_FILE deploy -y $COMMON_OPTS # Remote commands work from an empty directory without a supplied lock. mkdir "$REMOTE/empty" "$REMOTE/home" @@ -26,7 +26,7 @@ for command in run execute synthesize; do args=(synthesize some_sample_leo_program.aleo --skip mint --skip dynamic_transfer) ;; esac - if ! "$LEO_BIN" --disable-update-check --path "$REMOTE/empty" --home "$REMOTE/home" "${args[@]}" \ + if ! "$LEO_BIN" --path "$REMOTE/empty" --home "$REMOTE/home" "${args[@]}" \ --network testnet --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" > "$REMOTE/output" 2>&1; then cat "$REMOTE/output" exit 1 @@ -41,7 +41,7 @@ done # An extra remote program also needs no hand-written pin. sed 's/some_sample_leo_program/local_probe/g' "$ALEO_FILE" > "$REMOTE/empty/local_probe.aleo" -if ! "$LEO_BIN" --disable-update-check --path "$REMOTE/empty/local_probe.aleo" --home "$REMOTE/home" \ +if ! "$LEO_BIN" --path "$REMOTE/empty/local_probe.aleo" --home "$REMOTE/home" \ run main 1u32 2u32 --with some_sample_leo_program.aleo --network testnet \ --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" --private-key "$PRIVATE_KEY" > "$REMOTE/output" 2>&1; then cat "$REMOTE/output" @@ -53,8 +53,8 @@ test ! -e "$REMOTE/empty/leo.lock" echo "Remote run, execute, synthesize, and --with work without a supplied lock." # Keep an automatically generated dependency lock at edition zero for the upgrade check. -"$LEO_BIN" --disable-update-check --path "$REMOTE/lock_consumer" new lock_consumer > "$REMOTE/output" 2>&1 -if ! "$LEO_BIN" --disable-update-check --path "$REMOTE/lock_consumer" --home "$REMOTE/home" \ +"$LEO_BIN" --path "$REMOTE/lock_consumer" new lock_consumer > "$REMOTE/output" 2>&1 +if ! "$LEO_BIN" --path "$REMOTE/lock_consumer" --home "$REMOTE/home" \ add some_sample_leo_program --network --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" \ > "$REMOTE/output" 2>&1; then cat "$REMOTE/output" @@ -77,7 +77,7 @@ test "$(curl -fsS "http://localhost:${LEO_DEVNODE_PORT:-3030}/testnet/program/so cmp "$REMOTE/lock_consumer/leo.lock" "$REMOTE/original.lock" # A dry run reports the new edition without changing the lock or manifest. -if ! "$LEO_BIN" --disable-update-check --path "$REMOTE/lock_consumer" --home "$REMOTE/home" \ +if ! "$LEO_BIN" --path "$REMOTE/lock_consumer" --home "$REMOTE/home" \ update some_sample_leo_program --dry-run --network testnet --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" \ --network-retries 0 > "$REMOTE/output" 2>&1; then cat "$REMOTE/output" @@ -87,7 +87,7 @@ grep -Fq 'Would update some_sample_leo_program.aleo (testnet) edition 0 -> 2' "$ cmp "$REMOTE/lock_consumer/leo.lock" "$REMOTE/original.lock" cmp "$REMOTE/lock_consumer/program.json" "$REMOTE/original.json" -if ! "$LEO_BIN" --disable-update-check --path "$REMOTE/lock_consumer" --home "$REMOTE/home" \ +if ! "$LEO_BIN" --path "$REMOTE/lock_consumer" --home "$REMOTE/home" \ update some_sample_leo_program --network testnet --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" \ --network-retries 0 > "$REMOTE/output" 2>&1; then cat "$REMOTE/output" @@ -98,7 +98,7 @@ grep -Fq '"edition": 2' "$REMOTE/lock_consumer/leo.lock" cmp "$REMOTE/lock_consumer/program.json" "$REMOTE/original.json" # A manifest constraint keeps an exact edition fixed during a full update. -if ! "$LEO_BIN" --disable-update-check --path "$REMOTE/lock_consumer" --home "$REMOTE/home" \ +if ! "$LEO_BIN" --path "$REMOTE/lock_consumer" --home "$REMOTE/home" \ add some_sample_leo_program --edition 0 --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" \ > "$REMOTE/output" 2>&1; then cat "$REMOTE/output" @@ -107,7 +107,7 @@ fi grep -Fq '"edition": 0' "$REMOTE/lock_consumer/leo.lock" cp "$REMOTE/lock_consumer/leo.lock" "$REMOTE/exact.lock" cp "$REMOTE/lock_consumer/program.json" "$REMOTE/exact.json" -if ! "$LEO_BIN" --disable-update-check --path "$REMOTE/lock_consumer" --home "$REMOTE/home" \ +if ! "$LEO_BIN" --path "$REMOTE/lock_consumer" --home "$REMOTE/home" \ update --network testnet --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" \ --network-retries 0 > "$REMOTE/output" 2>&1; then cat "$REMOTE/output" diff --git a/tests/expectations/cli/test_deploy_rename/COMMANDS b/tests/expectations/cli/test_deploy_rename/COMMANDS index 4080c9ed57d..2f1bddaa200 100755 --- a/tests/expectations/cli/test_deploy_rename/COMMANDS +++ b/tests/expectations/cli/test_deploy_rename/COMMANDS @@ -4,14 +4,14 @@ LEO_BIN=${1} PRIVATE_KEY="APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH" ENDPOINT="http://localhost:${LEO_DEVNODE_PORT:-3030}" -COMMON_OPTS="--disable-update-check --network testnet --endpoint $ENDPOINT --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" +COMMON_OPTS="--network testnet --endpoint $ENDPOINT --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" # The package declares `original_prog.aleo`, but `--rename` recompiles and deploys # it on-chain under a different identity. $LEO_BIN deploy -y --broadcast --rename renamed_prog $COMMON_OPTS # Confirm the renamed program actually landed on-chain under its new name. -$LEO_BIN query --disable-update-check --network testnet --endpoint $ENDPOINT program renamed_prog.aleo +$LEO_BIN query --network testnet --endpoint $ENDPOINT program renamed_prog.aleo # Execute the renamed program from the original project directory. The local # package still declares `original_prog`, so the qualified name resolves to a diff --git a/tests/expectations/cli/test_deploy_rename_conflict/COMMANDS b/tests/expectations/cli/test_deploy_rename_conflict/COMMANDS index 4e28c013c2a..eb2b77dcd0b 100755 --- a/tests/expectations/cli/test_deploy_rename_conflict/COMMANDS +++ b/tests/expectations/cli/test_deploy_rename_conflict/COMMANDS @@ -2,7 +2,7 @@ LEO_BIN=${1} PRIVATE_KEY="APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH" -COMMON_OPTS="--disable-update-check --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" +COMMON_OPTS="--network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" # Renaming the primary program onto a dependency's name must be rejected before # any network interaction, so this test manages no devnode (see SKIP_DEVNODE). diff --git a/tests/expectations/cli/test_deploy_rename_rejected/COMMANDS b/tests/expectations/cli/test_deploy_rename_rejected/COMMANDS index 6e5c127424a..1c9df829bb0 100755 --- a/tests/expectations/cli/test_deploy_rename_rejected/COMMANDS +++ b/tests/expectations/cli/test_deploy_rename_rejected/COMMANDS @@ -2,7 +2,7 @@ LEO_BIN=${1} PRIVATE_KEY="APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH" -COMMON_OPTS="--disable-update-check --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" +COMMON_OPTS="--network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" # Both rejections fire before any network interaction, so this test manages no # devnode (see SKIP_DEVNODE). diff --git a/tests/expectations/cli/test_deploy_rename_workspace_member/COMMANDS b/tests/expectations/cli/test_deploy_rename_workspace_member/COMMANDS index f14215c29bb..c84f3acfaef 100755 --- a/tests/expectations/cli/test_deploy_rename_workspace_member/COMMANDS +++ b/tests/expectations/cli/test_deploy_rename_workspace_member/COMMANDS @@ -3,7 +3,7 @@ LEO_BIN=${1} PRIVATE_KEY="APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH" ENDPOINT="http://localhost:${LEO_DEVNODE_PORT:-3030}" -COMMON_OPTS="--disable-update-check --network testnet --endpoint $ENDPOINT --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" +COMMON_OPTS="--network testnet --endpoint $ENDPOINT --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" # Scoping the deploy to a single workspace member (here via `--package`) makes # `--rename` valid: the dependency-free `token` member is deployed on-chain as @@ -11,4 +11,4 @@ COMMON_OPTS="--disable-update-check --network testnet --endpoint $ENDPOINT --pri $LEO_BIN deploy -y --broadcast --package token --rename renamed_token $COMMON_OPTS # Confirm the renamed member landed on-chain under its new name. -$LEO_BIN query --disable-update-check --network testnet --endpoint $ENDPOINT program renamed_token.aleo +$LEO_BIN query --network testnet --endpoint $ENDPOINT program renamed_token.aleo diff --git a/tests/expectations/cli/test_deploy_rename_workspace_rejected/COMMANDS b/tests/expectations/cli/test_deploy_rename_workspace_rejected/COMMANDS index 9d3490fda2e..3341448004f 100755 --- a/tests/expectations/cli/test_deploy_rename_workspace_rejected/COMMANDS +++ b/tests/expectations/cli/test_deploy_rename_workspace_rejected/COMMANDS @@ -2,7 +2,7 @@ LEO_BIN=${1} PRIVATE_KEY="APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH" -COMMON_OPTS="--disable-update-check --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" +COMMON_OPTS="--network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" # A single `--rename` cannot apply across multiple workspace members. Deploying # the whole workspace (from the root) with `--rename` must be rejected before any diff --git a/tests/expectations/cli/test_devnet/COMMANDS b/tests/expectations/cli/test_devnet/COMMANDS index 02305df9d8c..308e3cb976e 100755 --- a/tests/expectations/cli/test_devnet/COMMANDS +++ b/tests/expectations/cli/test_devnet/COMMANDS @@ -20,7 +20,7 @@ cleanup() { } trap cleanup EXIT -$LEO_BIN --disable-update-check devnet \ +$LEO_BIN devnet \ --snarkos "$SNARKOS" \ --num-clients 0 \ --rest-port "$REST_PORT" \ @@ -92,5 +92,5 @@ kill "$PID" 2>/dev/null && wait "$PID" 2>/dev/null PID="" # Clean devnet storage and report. -$LEO_BIN --disable-update-check devnet --clean-only --snarkos "$SNARKOS" --storage "$STORAGE" --yes >/dev/null 2>&1 +$LEO_BIN devnet --clean-only --snarkos "$SNARKOS" --storage "$STORAGE" --yes >/dev/null 2>&1 echo "Leo devnet clean succeeded" diff --git a/tests/expectations/cli/test_devnode/COMMANDS b/tests/expectations/cli/test_devnode/COMMANDS index 4341790ac68..21284969188 100755 --- a/tests/expectations/cli/test_devnode/COMMANDS +++ b/tests/expectations/cli/test_devnode/COMMANDS @@ -15,7 +15,7 @@ cleanup() { trap cleanup EXIT # Start devnode on the allocated port. -$LEO_BIN --disable-update-check devnode start \ +$LEO_BIN devnode start \ --socket-addr "127.0.0.1:${PORT}" \ --private-key APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH \ >/dev/null 2>&1 & @@ -39,7 +39,7 @@ fi echo "Leo devnode started" # Advance the ledger by 5 blocks. -$LEO_BIN --disable-update-check devnode advance 5 \ +$LEO_BIN devnode advance 5 \ --socket-addr "127.0.0.1:${PORT}" \ >/dev/null 2>&1 diff --git a/tests/expectations/cli/test_devnode_persistent_storage/COMMANDS b/tests/expectations/cli/test_devnode_persistent_storage/COMMANDS index b5555d078b8..3c02a792002 100755 --- a/tests/expectations/cli/test_devnode_persistent_storage/COMMANDS +++ b/tests/expectations/cli/test_devnode_persistent_storage/COMMANDS @@ -35,7 +35,7 @@ wait_for_devnode() { } # ── Start devnode 1 with persistent storage ──────────────────────────────────── -$LEO_BIN --disable-update-check devnode start \ +$LEO_BIN devnode start \ --socket-addr "127.0.0.1:${PORT}" \ --private-key "$PRIVATE_KEY" \ --storage "$LEDGER_DIR" \ @@ -46,7 +46,7 @@ wait_for_devnode echo "--- Devnode 1 started ---" # ── Deploy and execute to write mapping state ────────────────────────────────── -COMMON_OPTS="--disable-update-check --broadcast --network testnet --endpoint http://localhost:${PORT} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" +COMMON_OPTS="--broadcast --network testnet --endpoint http://localhost:${PORT} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" echo "--- leo deploy ---" $LEO_BIN deploy -y $COMMON_OPTS @@ -87,7 +87,7 @@ while [ $SECONDS -lt $DEADLINE ]; do done # ── Start devnode 2 with the same persistent storage ────────────────────────── -$LEO_BIN --disable-update-check devnode start \ +$LEO_BIN devnode start \ --socket-addr "127.0.0.1:${PORT}" \ --private-key "$PRIVATE_KEY" \ --storage "$LEDGER_DIR" \ diff --git a/tests/expectations/cli/test_devnode_view/COMMANDS b/tests/expectations/cli/test_devnode_view/COMMANDS index d1bcbdc28b9..679b2d2a653 100755 --- a/tests/expectations/cli/test_devnode_view/COMMANDS +++ b/tests/expectations/cli/test_devnode_view/COMMANDS @@ -10,7 +10,7 @@ PROG="test_devnode_view.aleo" # Deploy the program carrying the `view fn` to the framework-managed devnode and # block until it is confirmed on-chain. -$LEO_BIN deploy -y --disable-update-check --broadcast --network testnet \ +$LEO_BIN deploy -y --broadcast --network testnet \ --endpoint $ENDPOINT --private-key $PRIVATE_KEY \ --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 >/dev/null 2>&1 diff --git a/tests/expectations/cli/test_duplicate_lib_name/COMMANDS b/tests/expectations/cli/test_duplicate_lib_name/COMMANDS index 48c28f9e02b..6966d5e1799 100755 --- a/tests/expectations/cli/test_duplicate_lib_name/COMMANDS +++ b/tests/expectations/cli/test_duplicate_lib_name/COMMANDS @@ -5,5 +5,5 @@ LEO_BIN=${1} echo "--- leo build main (should fail: conflicting library name 'helper') ---" | tee /dev/stderr cd main || exit 1 -${LEO_BIN} --disable-update-check build +${LEO_BIN} build echo "build exit code: $?" diff --git a/tests/expectations/cli/test_dynamic_call_with_flag/COMMANDS b/tests/expectations/cli/test_dynamic_call_with_flag/COMMANDS index 001887aab73..4ce92330f40 100755 --- a/tests/expectations/cli/test_dynamic_call_with_flag/COMMANDS +++ b/tests/expectations/cli/test_dynamic_call_with_flag/COMMANDS @@ -1,7 +1,6 @@ #!/usr/bin/env bash LEO_BIN=${1} -COMMON_OPTS="--disable-update-check" PROG="'extra_prog'" NET="'aleo'" @@ -9,16 +8,16 @@ FUNC="'dbl'" INPUT="7u64" echo "--- run WITHOUT --with should fail ---" -${LEO_BIN} ${COMMON_OPTS} run main ${PROG} ${NET} ${FUNC} ${INPUT} 2>&1 || true +${LEO_BIN} run main ${PROG} ${NET} ${FUNC} ${INPUT} 2>&1 || true echo "--- run WITH --with should succeed ---" -${LEO_BIN} ${COMMON_OPTS} run main ${PROG} ${NET} ${FUNC} ${INPUT} --with ./extra_prog.aleo +${LEO_BIN} run main ${PROG} ${NET} ${FUNC} ${INPUT} --with ./extra_prog.aleo echo "--- run a standalone Aleo file ---" -${LEO_BIN} ${COMMON_OPTS} --path ./extra_prog.aleo run dbl ${INPUT} +${LEO_BIN} --path ./extra_prog.aleo run dbl ${INPUT} echo "--- write standalone Aleo run JSON output ---" -${LEO_BIN} ${COMMON_OPTS} --path ./extra_prog.aleo --json-output run dbl ${INPUT} +${LEO_BIN} --path ./extra_prog.aleo --json-output run dbl ${INPUT} cat ./build/json-outputs/run.json rm ./build/json-outputs/run.json diff --git a/tests/expectations/cli/test_execute_view_rejected/COMMANDS b/tests/expectations/cli/test_execute_view_rejected/COMMANDS index 8934b260918..92188f09659 100755 --- a/tests/expectations/cli/test_execute_view_rejected/COMMANDS +++ b/tests/expectations/cli/test_execute_view_rejected/COMMANDS @@ -12,6 +12,6 @@ PRIVATE_KEY="APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH" ENDPOINT="http://127.0.0.1:1" echo "--- execute on a view fn should fail with a clear message ---" -${LEO_BIN} --disable-update-check execute --network testnet --endpoint $ENDPOINT --private-key $PRIVATE_KEY get_balance aleo1rhgdu77hgyqd3xjj8ucu3jj9r2krwz6mnzyd80gncr5fxcwlh5rsvzp9px 2>&1 || true +${LEO_BIN} execute --network testnet --endpoint $ENDPOINT --private-key $PRIVATE_KEY get_balance aleo1rhgdu77hgyqd3xjj8ucu3jj9r2krwz6mnzyd80gncr5fxcwlh5rsvzp9px 2>&1 || true exit 0 diff --git a/tests/expectations/cli/test_external_constructor_ctx/COMMANDS b/tests/expectations/cli/test_external_constructor_ctx/COMMANDS index 977598728c8..15039db3336 100755 --- a/tests/expectations/cli/test_external_constructor_ctx/COMMANDS +++ b/tests/expectations/cli/test_external_constructor_ctx/COMMANDS @@ -7,9 +7,9 @@ LEO_BIN=${1} cd child || exit 1 echo "--- leo build child ---" -${LEO_BIN} --disable-update-check build --checksums +${LEO_BIN} build --checksums cd ../parent || exit 1 echo "--- leo build parent ---" -${LEO_BIN} --disable-update-check build --checksums +${LEO_BIN} build --checksums diff --git a/tests/expectations/cli/test_external_program_submodule/COMMANDS b/tests/expectations/cli/test_external_program_submodule/COMMANDS index e25169f0edf..c65ac6b026c 100755 --- a/tests/expectations/cli/test_external_program_submodule/COMMANDS +++ b/tests/expectations/cli/test_external_program_submodule/COMMANDS @@ -8,4 +8,4 @@ LEO_BIN=${1} cd consumer || exit 1 echo "--- leo build (access provider.aleo submodule items) ---" -${LEO_BIN} --disable-update-check build --checksums +${LEO_BIN} build --checksums diff --git a/tests/expectations/cli/test_external_toplevel_closure/COMMANDS b/tests/expectations/cli/test_external_toplevel_closure/COMMANDS index 76385710574..e953704c68e 100755 --- a/tests/expectations/cli/test_external_toplevel_closure/COMMANDS +++ b/tests/expectations/cli/test_external_toplevel_closure/COMMANDS @@ -10,4 +10,4 @@ LEO_BIN=${1} cd parent || exit 1 echo "--- leo build (call child.aleo top-level closure) ---" -${LEO_BIN} --disable-update-check build +${LEO_BIN} build diff --git a/tests/expectations/cli/test_failure_exit_code/COMMANDS b/tests/expectations/cli/test_failure_exit_code/COMMANDS index 0ce05e10a73..2ee615b7deb 100755 --- a/tests/expectations/cli/test_failure_exit_code/COMMANDS +++ b/tests/expectations/cli/test_failure_exit_code/COMMANDS @@ -2,4 +2,4 @@ LEO_BIN=${1} -${LEO_BIN} --disable-update-check test +${LEO_BIN} test diff --git a/tests/expectations/cli/test_fmt/COMMANDS b/tests/expectations/cli/test_fmt/COMMANDS index 29a16761ce3..93629ec91e3 100755 --- a/tests/expectations/cli/test_fmt/COMMANDS +++ b/tests/expectations/cli/test_fmt/COMMANDS @@ -3,12 +3,12 @@ LEO_BIN=${1} echo "--- fmt program ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check fmt --check || true -${LEO_BIN} --disable-update-check fmt -${LEO_BIN} --disable-update-check fmt --check +${LEO_BIN} fmt --check || true +${LEO_BIN} fmt +${LEO_BIN} fmt --check echo "--- fmt library ---" | tee /dev/stderr cd ugly_lib || exit 1 -${LEO_BIN} --disable-update-check fmt --check || true -${LEO_BIN} --disable-update-check fmt -${LEO_BIN} --disable-update-check fmt --check +${LEO_BIN} fmt --check || true +${LEO_BIN} fmt +${LEO_BIN} fmt --check diff --git a/tests/expectations/cli/test_git_dependency/COMMANDS b/tests/expectations/cli/test_git_dependency/COMMANDS index ac467c7b523..f821343f17c 100755 --- a/tests/expectations/cli/test_git_dependency/COMMANDS +++ b/tests/expectations/cli/test_git_dependency/COMMANDS @@ -29,7 +29,7 @@ printf 'program gitprog.aleo {\n @noupgrade\n constructor() {}\n fn run # An Aleo bytecode file at the root: compile a throwaway program so the bytecode is valid for the # current compiler, then place its `.aleo` at the repository root. GEN=$(mktemp -d) -( cd "${GEN}" && ${LEO_BIN} --disable-update-check --home "${HOMEDIR}" new gitbytes > /dev/null 2>&1 && cd gitbytes && ${LEO_BIN} --disable-update-check --home "${HOMEDIR}" build > /dev/null 2>&1 ) +( cd "${GEN}" && ${LEO_BIN} --home "${HOMEDIR}" new gitbytes > /dev/null 2>&1 && cd gitbytes && ${LEO_BIN} --home "${HOMEDIR}" build > /dev/null 2>&1 ) cp "$(find "${GEN}/gitbytes/build" -name 'gitbytes.aleo' | head -1)" "${REPO}/gitbytes.aleo" ${GIT} -C "${REPO}" init -q @@ -51,7 +51,7 @@ OUT=$(mktemp) ERR=$(mktemp) # Run `leo`, redacting the git URL/commits from both its stdout and stderr while preserving order. leo_redacted() { - ${LEO_BIN} --disable-update-check --home "${HOMEDIR}" "$@" >"${OUT}" 2>"${ERR}" + ${LEO_BIN} --home "${HOMEDIR}" "$@" >"${OUT}" 2>"${ERR}" local rc=$? redact <"${OUT}" redact <"${ERR}" >&2 diff --git a/tests/expectations/cli/test_identifier_input/COMMANDS b/tests/expectations/cli/test_identifier_input/COMMANDS index 9f2b8b6e42b..39b7d714396 100755 --- a/tests/expectations/cli/test_identifier_input/COMMANDS +++ b/tests/expectations/cli/test_identifier_input/COMMANDS @@ -2,10 +2,10 @@ LEO_BIN=${1} PRIVATE_KEY="APrivateKey1zkp2RWGDcde3efb89rjhME1VYA8QMxcxep5DShNBR6n8Yjh" -COMMON_OPTS="--disable-update-check --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" +COMMON_OPTS="--network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" echo "--- leo run with identifier input ---" -${LEO_BIN} --disable-update-check run main "'hello'" +${LEO_BIN} run main "'hello'" echo "--- leo execute with identifier input ---" ${LEO_BIN} execute -y $COMMON_OPTS main "'hello'" diff --git a/tests/expectations/cli/test_interface_abi_from_library/COMMANDS b/tests/expectations/cli/test_interface_abi_from_library/COMMANDS index 789f94721f4..5baafe1479f 100755 --- a/tests/expectations/cli/test_interface_abi_from_library/COMMANDS +++ b/tests/expectations/cli/test_interface_abi_from_library/COMMANDS @@ -7,10 +7,10 @@ LEO_BIN=${1} cd my_program || exit 1 echo "--- leo add --local (add lib dep to program) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add my_lib --local ../my_lib +${LEO_BIN} add my_lib --local ../my_lib echo "--- leo build ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check build +${LEO_BIN} build echo "build exit code: $?" echo "--- program ABI ---" | tee /dev/stderr diff --git a/tests/expectations/cli/test_interface_abi_inheritance/COMMANDS b/tests/expectations/cli/test_interface_abi_inheritance/COMMANDS index 867e63ae068..11c4559aece 100755 --- a/tests/expectations/cli/test_interface_abi_inheritance/COMMANDS +++ b/tests/expectations/cli/test_interface_abi_inheritance/COMMANDS @@ -6,7 +6,7 @@ LEO_BIN=${1} echo "--- leo build (interface inheritance) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check build +${LEO_BIN} build echo "build exit code: $?" echo "--- program ABI ---" | tee /dev/stderr diff --git a/tests/expectations/cli/test_interface_abi_local/COMMANDS b/tests/expectations/cli/test_interface_abi_local/COMMANDS index 96383eec5ea..d3dfdad5935 100755 --- a/tests/expectations/cli/test_interface_abi_local/COMMANDS +++ b/tests/expectations/cli/test_interface_abi_local/COMMANDS @@ -7,7 +7,7 @@ LEO_BIN=${1} echo "--- leo build (program with local interfaces) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check build +${LEO_BIN} build echo "build exit code: $?" echo "--- program ABI ---" | tee /dev/stderr diff --git a/tests/expectations/cli/test_invalid_literal_rejection/COMMANDS b/tests/expectations/cli/test_invalid_literal_rejection/COMMANDS index 4f3d0a1cd04..9ae582a5963 100755 --- a/tests/expectations/cli/test_invalid_literal_rejection/COMMANDS +++ b/tests/expectations/cli/test_invalid_literal_rejection/COMMANDS @@ -1,22 +1,21 @@ #!/usr/bin/env bash LEO_BIN=${1} -COMMON_OPTS="--disable-update-check" echo "--- valid field literal should succeed ---" | tee /dev/stderr -${LEO_BIN} ${COMMON_OPTS} run main 42field +${LEO_BIN} run main 42field echo "--- truefield should be rejected ---" | tee /dev/stderr -${LEO_BIN} ${COMMON_OPTS} run main truefield +${LEO_BIN} run main truefield echo "--- empty prefix field should be rejected ---" | tee /dev/stderr -${LEO_BIN} ${COMMON_OPTS} run main field +${LEO_BIN} run main field echo "--- falsescalar should be rejected ---" | tee /dev/stderr -${LEO_BIN} ${COMMON_OPTS} run main falsescalar +${LEO_BIN} run main falsescalar echo "--- abcu64 should be rejected ---" | tee /dev/stderr -${LEO_BIN} ${COMMON_OPTS} run main abcu64 +${LEO_BIN} run main abcu64 # Exit 0 so the test framework sees a clean exit; the assertions # are captured via STDOUT/STDERR comparison. diff --git a/tests/expectations/cli/test_json/COMMANDS b/tests/expectations/cli/test_json/COMMANDS index fe1038d7f70..31df715b6b7 100755 --- a/tests/expectations/cli/test_json/COMMANDS +++ b/tests/expectations/cli/test_json/COMMANDS @@ -3,13 +3,13 @@ LEO_BIN=${1} PRIVATE_KEY="APrivateKey1zkp2RWGDcde3efb89rjhME1VYA8QMxcxep5DShNBR6n8Yjh" -COMMON_OPTS="--disable-update-check --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" +COMMON_OPTS="--network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" # `synthesize` and `query` don't sign or set consensus, so they only take the connection options. -CONN_OPTS="--disable-update-check --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030}" +CONN_OPTS="--network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030}" -$LEO_BIN --disable-update-check run --json-output main 1u32 2u32 +$LEO_BIN run --json-output main 1u32 2u32 -$LEO_BIN --disable-update-check test --json-output +$LEO_BIN test --json-output $LEO_BIN synthesize --json-output --local test_json_program.aleo $CONN_OPTS @@ -21,4 +21,4 @@ $LEO_BIN query --json-output program test_json_program.aleo $CONN_OPTS $LEO_BIN upgrade -y --json-output --broadcast $COMMON_OPTS -$LEO_BIN --disable-update-check build --json-output --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} +$LEO_BIN build --json-output --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} diff --git a/tests/expectations/cli/test_lib_program_test/COMMANDS b/tests/expectations/cli/test_lib_program_test/COMMANDS index dff986d52f8..6838d61271b 100755 --- a/tests/expectations/cli/test_lib_program_test/COMMANDS +++ b/tests/expectations/cli/test_lib_program_test/COMMANDS @@ -14,4 +14,4 @@ LEO_BIN=${1} cd my_program || exit 1 echo "--- leo test (should succeed) ---" -${LEO_BIN} --disable-update-check test +${LEO_BIN} test diff --git a/tests/expectations/cli/test_lib_submodule_with_tests/COMMANDS b/tests/expectations/cli/test_lib_submodule_with_tests/COMMANDS index be5a01056d6..9a067ef3250 100755 --- a/tests/expectations/cli/test_lib_submodule_with_tests/COMMANDS +++ b/tests/expectations/cli/test_lib_submodule_with_tests/COMMANDS @@ -8,4 +8,4 @@ LEO_BIN=${1} cd my_lib || exit 1 echo "--- leo test on a library with submodules ---" -${LEO_BIN} --disable-update-check test +${LEO_BIN} test diff --git a/tests/expectations/cli/test_lib_with_tests/COMMANDS b/tests/expectations/cli/test_lib_with_tests/COMMANDS index 7ac13aca8d1..f5821b7ad38 100755 --- a/tests/expectations/cli/test_lib_with_tests/COMMANDS +++ b/tests/expectations/cli/test_lib_with_tests/COMMANDS @@ -8,4 +8,4 @@ LEO_BIN=${1} cd my_lib || exit 1 echo "--- leo test on a library with tests/ ---" -${LEO_BIN} --disable-update-check test +${LEO_BIN} test diff --git a/tests/expectations/cli/test_library/COMMANDS b/tests/expectations/cli/test_library/COMMANDS index 209c209725f..694e0f48884 100755 --- a/tests/expectations/cli/test_library/COMMANDS +++ b/tests/expectations/cli/test_library/COMMANDS @@ -5,40 +5,40 @@ LEO_BIN=${1} cd top_lib || exit 1 echo "--- leo add --local (should succeed) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add base_lib --local ../base_lib +${LEO_BIN} add base_lib --local ../base_lib echo "--- leo add --network on a library (should fail) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add credits --network +${LEO_BIN} add credits --network echo "add network to library exit code: $?" echo "--- leo build (should succeed) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check build +${LEO_BIN} build echo "--- leo run (should fail) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check run main +${LEO_BIN} run main echo "run exit code: $?" echo "--- leo execute (should fail) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check execute main --network testnet --endpoint http://localhost:3030 --private-key APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH --yes +${LEO_BIN} execute main --network testnet --endpoint http://localhost:3030 --private-key APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH --yes echo "execute exit code: $?" echo "--- leo deploy (should fail) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check deploy --network testnet --endpoint http://localhost:3030 --private-key APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH --yes +${LEO_BIN} deploy --network testnet --endpoint http://localhost:3030 --private-key APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH --yes echo "deploy exit code: $?" echo "--- leo test (should fail) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check test +${LEO_BIN} test echo "test exit code: $?" echo "--- leo upgrade (should fail) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check upgrade --network testnet +${LEO_BIN} upgrade --network testnet echo "upgrade exit code: $?" echo "--- leo clean on library (should succeed) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check clean +${LEO_BIN} clean echo "clean exit code: $?" echo "--- leo run with library dep (should succeed) ---" | tee /dev/stderr cd ../program_using_lib || exit 1 -${LEO_BIN} --disable-update-check run main 1u32 -${LEO_BIN} --disable-update-check clean +${LEO_BIN} run main 1u32 +${LEO_BIN} clean diff --git a/tests/expectations/cli/test_library_build/COMMANDS b/tests/expectations/cli/test_library_build/COMMANDS index 6009b728752..7fba5626554 100755 --- a/tests/expectations/cli/test_library_build/COMMANDS +++ b/tests/expectations/cli/test_library_build/COMMANDS @@ -5,11 +5,11 @@ LEO_BIN=${1} echo "--- leo add --local (add lib dep to program) ---" | tee /dev/stderr cd my_program || exit 1 -${LEO_BIN} --disable-update-check add my_lib --local ../my_lib +${LEO_BIN} add my_lib --local ../my_lib echo "--- leo build (should succeed) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check build +${LEO_BIN} build echo "--- bytecode (should NOT contain 'import my_lib') ---" | tee /dev/stderr diff --git a/tests/expectations/cli/test_library_build_interface_cycle/COMMANDS b/tests/expectations/cli/test_library_build_interface_cycle/COMMANDS index 53e472cce8c..f1cba948862 100755 --- a/tests/expectations/cli/test_library_build_interface_cycle/COMMANDS +++ b/tests/expectations/cli/test_library_build_interface_cycle/COMMANDS @@ -8,5 +8,5 @@ LEO_BIN=${1} cd my_lib || exit 1 echo "--- leo build (should fail: cyclic interfaces) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check build +${LEO_BIN} build echo "build exit code: $?" diff --git a/tests/expectations/cli/test_library_build_type_error/COMMANDS b/tests/expectations/cli/test_library_build_type_error/COMMANDS index 693dc44e368..0602d9b171b 100755 --- a/tests/expectations/cli/test_library_build_type_error/COMMANDS +++ b/tests/expectations/cli/test_library_build_type_error/COMMANDS @@ -8,5 +8,5 @@ LEO_BIN=${1} cd my_lib || exit 1 echo "--- leo build (should fail: type error in library) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check build +${LEO_BIN} build echo "build exit code: $?" diff --git a/tests/expectations/cli/test_library_build_undefined_name/COMMANDS b/tests/expectations/cli/test_library_build_undefined_name/COMMANDS index 2b1ef7ca112..8661bf1846c 100755 --- a/tests/expectations/cli/test_library_build_undefined_name/COMMANDS +++ b/tests/expectations/cli/test_library_build_undefined_name/COMMANDS @@ -8,5 +8,5 @@ LEO_BIN=${1} cd my_lib || exit 1 echo "--- leo build (should fail: undefined name in library) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check build +${LEO_BIN} build echo "build exit code: $?" diff --git a/tests/expectations/cli/test_library_build_valid_interface/COMMANDS b/tests/expectations/cli/test_library_build_valid_interface/COMMANDS index 7b3845cb873..1d87cee0abc 100755 --- a/tests/expectations/cli/test_library_build_valid_interface/COMMANDS +++ b/tests/expectations/cli/test_library_build_valid_interface/COMMANDS @@ -8,5 +8,5 @@ LEO_BIN=${1} cd my_lib || exit 1 echo "--- leo build (should succeed: valid interface library) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check build +${LEO_BIN} build echo "build exit code: $?" diff --git a/tests/expectations/cli/test_library_build_with_dep_lib/COMMANDS b/tests/expectations/cli/test_library_build_with_dep_lib/COMMANDS index c3751ec3c37..660f572ebd4 100755 --- a/tests/expectations/cli/test_library_build_with_dep_lib/COMMANDS +++ b/tests/expectations/cli/test_library_build_with_dep_lib/COMMANDS @@ -9,8 +9,8 @@ LEO_BIN=${1} cd top_lib || exit 1 echo "--- leo add --local base_lib (should succeed) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add base_lib --local ../base_lib +${LEO_BIN} add base_lib --local ../base_lib echo "--- leo build (should succeed: library-to-library reference) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check build +${LEO_BIN} build echo "build exit code: $?" diff --git a/tests/expectations/cli/test_library_circular/COMMANDS b/tests/expectations/cli/test_library_circular/COMMANDS index 297abfb7451..e0e54db53d2 100755 --- a/tests/expectations/cli/test_library_circular/COMMANDS +++ b/tests/expectations/cli/test_library_circular/COMMANDS @@ -5,15 +5,15 @@ LEO_BIN=${1} echo "--- add lib_b as dependency of lib_a ---" | tee /dev/stderr cd lib_a || exit 1 -${LEO_BIN} --disable-update-check add lib_b --local ../lib_b +${LEO_BIN} add lib_b --local ../lib_b echo "--- add lib_a as dependency of lib_b (creates cycle) ---" | tee /dev/stderr cd ../lib_b || exit 1 -${LEO_BIN} --disable-update-check add lib_a --local ../lib_a +${LEO_BIN} add lib_a --local ../lib_a echo "--- leo build lib_a (should fail: circular dependency) ---" | tee /dev/stderr cd ../lib_a || exit 1 -${LEO_BIN} --disable-update-check build +${LEO_BIN} build echo "build exit code: $?" diff --git a/tests/expectations/cli/test_library_parse_error/COMMANDS b/tests/expectations/cli/test_library_parse_error/COMMANDS index 80c8a6b15cc..48ac0f78554 100755 --- a/tests/expectations/cli/test_library_parse_error/COMMANDS +++ b/tests/expectations/cli/test_library_parse_error/COMMANDS @@ -6,9 +6,9 @@ cd my_program || exit 1 echo "--- leo add --local bad_lib ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add bad_lib --local ../bad_lib +${LEO_BIN} add bad_lib --local ../bad_lib echo "--- leo build (should fail: library has parse error) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check build +${LEO_BIN} build echo "build exit code: $?" diff --git a/tests/expectations/cli/test_library_submodule_access/COMMANDS b/tests/expectations/cli/test_library_submodule_access/COMMANDS index 6c4e54906b2..199f952cb3d 100755 --- a/tests/expectations/cli/test_library_submodule_access/COMMANDS +++ b/tests/expectations/cli/test_library_submodule_access/COMMANDS @@ -7,7 +7,7 @@ LEO_BIN=${1} echo "--- leo add --local (add shapes_lib dep to my_app) ---" cd my_app || exit 1 -${LEO_BIN} --disable-update-check add shapes_lib --local ../shapes_lib +${LEO_BIN} add shapes_lib --local ../shapes_lib echo "--- leo test (should pass all submodule assertions) ---" -${LEO_BIN} --disable-update-check test +${LEO_BIN} test diff --git a/tests/expectations/cli/test_multiple_test_files/COMMANDS b/tests/expectations/cli/test_multiple_test_files/COMMANDS index 0ce05e10a73..2ee615b7deb 100755 --- a/tests/expectations/cli/test_multiple_test_files/COMMANDS +++ b/tests/expectations/cli/test_multiple_test_files/COMMANDS @@ -2,4 +2,4 @@ LEO_BIN=${1} -${LEO_BIN} --disable-update-check test +${LEO_BIN} test diff --git a/tests/expectations/cli/test_network_retries/COMMANDS b/tests/expectations/cli/test_network_retries/COMMANDS index b4e13319cd0..de9612c5fa6 100755 --- a/tests/expectations/cli/test_network_retries/COMMANDS +++ b/tests/expectations/cli/test_network_retries/COMMANDS @@ -19,7 +19,7 @@ LEO_BIN=${1} DEAD_ENDPOINT="http://localhost:1" # One retry: expect exactly one "retrying" message in stderr. -STDERR1=$(${LEO_BIN} --disable-update-check query \ +STDERR1=$(${LEO_BIN} query \ --network testnet \ --endpoint "${DEAD_ENDPOINT}" \ --network-retries 1 \ @@ -28,7 +28,7 @@ COUNT1=$(echo "${STDERR1}" | grep -c "Network request failed, retrying" || true) echo "retries=1: retry_count=${COUNT1}" # Zero retries: expect no "retrying" message. -STDERR0=$(${LEO_BIN} --disable-update-check query \ +STDERR0=$(${LEO_BIN} query \ --network testnet \ --endpoint "${DEAD_ENDPOINT}" \ --network-retries 0 \ diff --git a/tests/expectations/cli/test_network_retries_flag/COMMANDS b/tests/expectations/cli/test_network_retries_flag/COMMANDS index 71f1056e95b..a7e1dc0be6d 100755 --- a/tests/expectations/cli/test_network_retries_flag/COMMANDS +++ b/tests/expectations/cli/test_network_retries_flag/COMMANDS @@ -6,7 +6,7 @@ LEO_BIN=${1} for cmd in build run execute deploy upgrade query synthesize; do - if ${LEO_BIN} --disable-update-check "${cmd}" --help 2>&1 | grep -qF -- '--network-retries'; then + if ${LEO_BIN} "${cmd}" --help 2>&1 | grep -qF -- '--network-retries'; then echo "${cmd}: --network-retries OK" else echo "${cmd}: --network-retries MISSING" diff --git a/tests/expectations/cli/test_run_view_rejected/COMMANDS b/tests/expectations/cli/test_run_view_rejected/COMMANDS index 293b3697898..0b5244dc0c4 100755 --- a/tests/expectations/cli/test_run_view_rejected/COMMANDS +++ b/tests/expectations/cli/test_run_view_rejected/COMMANDS @@ -5,12 +5,11 @@ # only checks the functions IndexMap, not views). LEO_BIN=${1} -COMMON_OPTS="--disable-update-check" echo "--- run on a view fn should fail with a clear message ---" -${LEO_BIN} ${COMMON_OPTS} run get_balance aleo1rhgdu77hgyqd3xjj8ucu3jj9r2krwz6mnzyd80gncr5fxcwlh5rsvzp9px 2>&1 || true +${LEO_BIN} run get_balance aleo1rhgdu77hgyqd3xjj8ucu3jj9r2krwz6mnzyd80gncr5fxcwlh5rsvzp9px 2>&1 || true echo "--- run on a regular fn should still work ---" -${LEO_BIN} ${COMMON_OPTS} run noop 1u32 2>&1 | tail -n 5 +${LEO_BIN} run noop 1u32 2>&1 | tail -n 5 exit 0 diff --git a/tests/expectations/cli/test_simple_build/COMMANDS b/tests/expectations/cli/test_simple_build/COMMANDS index 61759328265..5023dfe9f51 100755 --- a/tests/expectations/cli/test_simple_build/COMMANDS +++ b/tests/expectations/cli/test_simple_build/COMMANDS @@ -2,4 +2,4 @@ LEO_BIN=${1} -${LEO_BIN} --disable-update-check build --checksums +${LEO_BIN} build --checksums diff --git a/tests/expectations/cli/test_simple_test/COMMANDS b/tests/expectations/cli/test_simple_test/COMMANDS index 0ce05e10a73..2ee615b7deb 100755 --- a/tests/expectations/cli/test_simple_test/COMMANDS +++ b/tests/expectations/cli/test_simple_test/COMMANDS @@ -2,4 +2,4 @@ LEO_BIN=${1} -${LEO_BIN} --disable-update-check test +${LEO_BIN} test diff --git a/tests/expectations/cli/test_storage_from_unit_test/COMMANDS b/tests/expectations/cli/test_storage_from_unit_test/COMMANDS index c83fe3399ab..1d4af54f337 100755 --- a/tests/expectations/cli/test_storage_from_unit_test/COMMANDS +++ b/tests/expectations/cli/test_storage_from_unit_test/COMMANDS @@ -6,4 +6,4 @@ LEO_BIN=${1} -${LEO_BIN} --disable-update-check test +${LEO_BIN} test diff --git a/tests/expectations/cli/test_version/COMMANDS b/tests/expectations/cli/test_version/COMMANDS index aed85fce940..1300ca22727 100755 --- a/tests/expectations/cli/test_version/COMMANDS +++ b/tests/expectations/cli/test_version/COMMANDS @@ -2,4 +2,4 @@ LEO_BIN=${1} -${LEO_BIN} --disable-update-check --version +${LEO_BIN} --version diff --git a/tests/expectations/cli/test_with_record/COMMANDS b/tests/expectations/cli/test_with_record/COMMANDS index 81cc6e9b13d..19cecec4478 100755 --- a/tests/expectations/cli/test_with_record/COMMANDS +++ b/tests/expectations/cli/test_with_record/COMMANDS @@ -13,4 +13,4 @@ LEO_BIN=${1} cd my_program || exit 1 echo "--- leo test (program with record, should succeed) ---" -${LEO_BIN} --disable-update-check test +${LEO_BIN} test diff --git a/tests/expectations/cli/test_workspace_deploy/COMMANDS b/tests/expectations/cli/test_workspace_deploy/COMMANDS index 6c46525fb45..40d504259ed 100755 --- a/tests/expectations/cli/test_workspace_deploy/COMMANDS +++ b/tests/expectations/cli/test_workspace_deploy/COMMANDS @@ -2,6 +2,6 @@ LEO_BIN=${1} PRIVATE_KEY="APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH" -COMMON_OPTS="--disable-update-check --broadcast --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" +COMMON_OPTS="--broadcast --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" $LEO_BIN deploy -y $COMMON_OPTS diff --git a/tests/tests/cli/aleo_dep_imports_local_dep/COMMANDS b/tests/tests/cli/aleo_dep_imports_local_dep/COMMANDS index 3ba5b05025b..f4c781171ae 100755 --- a/tests/tests/cli/aleo_dep_imports_local_dep/COMMANDS +++ b/tests/tests/cli/aleo_dep_imports_local_dep/COMMANDS @@ -2,4 +2,4 @@ LEO_BIN=${1} -${LEO_BIN} --disable-update-check build +${LEO_BIN} build diff --git a/tests/tests/cli/aleo_dep_reversed_order/COMMANDS b/tests/tests/cli/aleo_dep_reversed_order/COMMANDS index 3ba5b05025b..f4c781171ae 100755 --- a/tests/tests/cli/aleo_dep_reversed_order/COMMANDS +++ b/tests/tests/cli/aleo_dep_reversed_order/COMMANDS @@ -2,4 +2,4 @@ LEO_BIN=${1} -${LEO_BIN} --disable-update-check build +${LEO_BIN} build diff --git a/tests/tests/cli/broadcast_error/COMMANDS b/tests/tests/cli/broadcast_error/COMMANDS index 37f25af16e3..6979dea26f4 100755 --- a/tests/tests/cli/broadcast_error/COMMANDS +++ b/tests/tests/cli/broadcast_error/COMMANDS @@ -6,7 +6,7 @@ LEO_BIN=${1} PRIVATE_KEY="APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH" -COMMON_OPTS="--disable-update-check --broadcast --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" +COMMON_OPTS="--broadcast --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" # First deploy should succeed $LEO_BIN deploy -y $COMMON_OPTS diff --git a/tests/tests/cli/build_checksums/COMMANDS b/tests/tests/cli/build_checksums/COMMANDS index 069af46e2f7..d9177af022c 100755 --- a/tests/tests/cli/build_checksums/COMMANDS +++ b/tests/tests/cli/build_checksums/COMMANDS @@ -7,14 +7,14 @@ trap 'rm -rf "${TMP_DIR}"' EXIT # `--checksums` prints the program checksum and the checksum of each entry and view function # (the `std::prog::function_checksum` targets) to stdout. -${LEO_BIN} --disable-update-check build --checksums >"${TMP_DIR}/stdout" 2>"${TMP_DIR}/stderr" +${LEO_BIN} build --checksums >"${TMP_DIR}/stdout" 2>"${TMP_DIR}/stderr" grep -qF "The program checksum is:" "${TMP_DIR}/stdout" grep -qF '`main` function checksum is:' "${TMP_DIR}/stdout" grep -qF '`peek` function checksum is:' "${TMP_DIR}/stdout" # Without the flag, no checksum lines are printed. -${LEO_BIN} --disable-update-check build >"${TMP_DIR}/stdout2" 2>"${TMP_DIR}/stderr2" +${LEO_BIN} build >"${TMP_DIR}/stdout2" 2>"${TMP_DIR}/stderr2" if grep -qF "checksum is:" "${TMP_DIR}/stdout2"; then echo "checksums printed without the --checksums flag" exit 1 diff --git a/tests/tests/cli/build_missing_constructor/COMMANDS b/tests/tests/cli/build_missing_constructor/COMMANDS index 19e76ae73d1..8ced5eaed1f 100755 --- a/tests/tests/cli/build_missing_constructor/COMMANDS +++ b/tests/tests/cli/build_missing_constructor/COMMANDS @@ -5,7 +5,7 @@ LEO_BIN=${1} TMP_DIR=$(mktemp -d) trap 'rm -rf "${TMP_DIR}"' EXIT -if ${LEO_BIN} --disable-update-check build >"${TMP_DIR}/stdout" 2>"${TMP_DIR}/stderr"; then +if ${LEO_BIN} build >"${TMP_DIR}/stdout" 2>"${TMP_DIR}/stderr"; then echo "leo build unexpectedly succeeded" exit 1 fi diff --git a/tests/tests/cli/leo_devnode_missing_private_key/COMMANDS b/tests/tests/cli/leo_devnode_missing_private_key/COMMANDS index d2893cd34ca..ad49b2ae2f0 100755 --- a/tests/tests/cli/leo_devnode_missing_private_key/COMMANDS +++ b/tests/tests/cli/leo_devnode_missing_private_key/COMMANDS @@ -2,4 +2,4 @@ LEO_BIN=${1} -${LEO_BIN} --disable-update-check devnode start +${LEO_BIN} devnode start diff --git a/tests/tests/cli/local_aleo_dependency/COMMANDS b/tests/tests/cli/local_aleo_dependency/COMMANDS index 3ba5b05025b..f4c781171ae 100755 --- a/tests/tests/cli/local_aleo_dependency/COMMANDS +++ b/tests/tests/cli/local_aleo_dependency/COMMANDS @@ -2,4 +2,4 @@ LEO_BIN=${1} -${LEO_BIN} --disable-update-check build +${LEO_BIN} build diff --git a/tests/tests/cli/multiple_leo_deps/COMMANDS b/tests/tests/cli/multiple_leo_deps/COMMANDS index 854f1aa895d..47525daf034 100755 --- a/tests/tests/cli/multiple_leo_deps/COMMANDS +++ b/tests/tests/cli/multiple_leo_deps/COMMANDS @@ -3,4 +3,4 @@ LEO_BIN=${1} cd parent || exit 1 -$LEO_BIN --disable-update-check build +$LEO_BIN build diff --git a/tests/tests/cli/network_dependency/COMMANDS b/tests/tests/cli/network_dependency/COMMANDS index a5715d7e6a3..b655111115b 100755 --- a/tests/tests/cli/network_dependency/COMMANDS +++ b/tests/tests/cli/network_dependency/COMMANDS @@ -5,7 +5,7 @@ set -e LEO_BIN=${1} PRIVATE_KEY="APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH" -COMMON_OPTS="-y --disable-update-check --broadcast --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" +COMMON_OPTS="-y --broadcast --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" cd test_program1 || exit 1 echo "DEPLOYMENT 1" diff --git a/tests/tests/cli/program_name_mismatch/COMMANDS b/tests/tests/cli/program_name_mismatch/COMMANDS index 3ba5b05025b..f4c781171ae 100755 --- a/tests/tests/cli/program_name_mismatch/COMMANDS +++ b/tests/tests/cli/program_name_mismatch/COMMANDS @@ -2,4 +2,4 @@ LEO_BIN=${1} -${LEO_BIN} --disable-update-check build +${LEO_BIN} build diff --git a/tests/tests/cli/test_abi_compat/COMMANDS b/tests/tests/cli/test_abi_compat/COMMANDS index 54c2887fdfe..bd524cc101b 100755 --- a/tests/tests/cli/test_abi_compat/COMMANDS +++ b/tests/tests/cli/test_abi_compat/COMMANDS @@ -10,40 +10,40 @@ run() { } # The standard is a JSON ABI; generate it from standard.aleo once up front. -${LEO_BIN} --disable-update-check abi standard.aleo -o std_abi >/dev/null +${LEO_BIN} abi standard.aleo -o std_abi >/dev/null STD=std_abi/standard.aleo.abi.json echo "=== satisfies: superset vs standard ===" -run ${LEO_BIN} --disable-update-check abi compatible.aleo --satisfies ${STD} +run ${LEO_BIN} abi compatible.aleo --satisfies ${STD} echo "" echo "=== satisfies: identical (standard vs standard) ===" -run ${LEO_BIN} --disable-update-check abi standard.aleo --satisfies ${STD} +run ${LEO_BIN} abi standard.aleo --satisfies ${STD} echo "" echo "=== does not satisfy: missing function, mapping, and struct ===" -run ${LEO_BIN} --disable-update-check abi missing.aleo --satisfies ${STD} +run ${LEO_BIN} abi missing.aleo --satisfies ${STD} echo "" echo "=== does not satisfy: differing function, record, and struct definitions ===" -run ${LEO_BIN} --disable-update-check abi differs.aleo --satisfies ${STD} +run ${LEO_BIN} abi differs.aleo --satisfies ${STD} echo "" echo "=== JSON report via --output (satisfied) ===" -run ${LEO_BIN} --disable-update-check abi compatible.aleo --satisfies ${STD} --output report_ok.json +run ${LEO_BIN} abi compatible.aleo --satisfies ${STD} --output report_ok.json cat report_ok.json echo "" echo "" echo "=== JSON report via --output (not satisfied) ===" -run ${LEO_BIN} --disable-update-check abi missing.aleo --satisfies ${STD} --output report_bad.json +run ${LEO_BIN} abi missing.aleo --satisfies ${STD} --output report_bad.json cat report_bad.json echo "" echo "" echo "=== error: --satisfies file does not exist ===" -run ${LEO_BIN} --disable-update-check abi compatible.aleo --satisfies does_not_exist.abi.json +run ${LEO_BIN} abi compatible.aleo --satisfies does_not_exist.abi.json echo "" echo "=== error: --satisfies given a .aleo file (only .abi.json is accepted) ===" -run ${LEO_BIN} --disable-update-check abi compatible.aleo --satisfies standard.aleo +run ${LEO_BIN} abi compatible.aleo --satisfies standard.aleo diff --git a/tests/tests/cli/test_abi_comprehensive/COMMANDS b/tests/tests/cli/test_abi_comprehensive/COMMANDS index 53fdf9f28f1..12928070ada 100755 --- a/tests/tests/cli/test_abi_comprehensive/COMMANDS +++ b/tests/tests/cli/test_abi_comprehensive/COMMANDS @@ -2,7 +2,7 @@ LEO_BIN=${1} -${LEO_BIN} --disable-update-check build --checksums +${LEO_BIN} build --checksums echo "" echo "=== build/abi_test/abi.json ===" diff --git a/tests/tests/cli/test_abi_from_aleo/COMMANDS b/tests/tests/cli/test_abi_from_aleo/COMMANDS index d8996b11c80..436c8b85389 100755 --- a/tests/tests/cli/test_abi_from_aleo/COMMANDS +++ b/tests/tests/cli/test_abi_from_aleo/COMMANDS @@ -3,28 +3,28 @@ LEO_BIN=${1} echo "=== simple.aleo ===" -${LEO_BIN} --disable-update-check abi simple.aleo +${LEO_BIN} abi simple.aleo echo "" echo "=== simple.aleo with --output ===" -${LEO_BIN} --disable-update-check abi simple.aleo -o simple_out +${LEO_BIN} abi simple.aleo -o simple_out cat simple_out/simple.aleo.abi.json echo "" echo "=== comprehensive.aleo ===" -${LEO_BIN} --disable-update-check abi comprehensive.aleo +${LEO_BIN} abi comprehensive.aleo echo "" echo "=== importer.aleo (auto-detected sibling imports/) ===" -${LEO_BIN} --disable-update-check abi importer.aleo +${LEO_BIN} abi importer.aleo echo "" echo "=== importer.aleo with explicit --imports-dir ===" -${LEO_BIN} --disable-update-check abi importer.aleo --imports-dir imports +${LEO_BIN} abi importer.aleo --imports-dir imports echo "" echo "=== importer.aleo with --output (bundle layout) ===" -${LEO_BIN} --disable-update-check abi importer.aleo -o importer_out +${LEO_BIN} abi importer.aleo -o importer_out echo "--- importer_out/importer.aleo.abi.json ---" cat importer_out/importer.aleo.abi.json echo "" diff --git a/tests/tests/cli/test_add/COMMANDS b/tests/tests/cli/test_add/COMMANDS index 64b51579195..b992e108e77 100755 --- a/tests/tests/cli/test_add/COMMANDS +++ b/tests/tests/cli/test_add/COMMANDS @@ -2,10 +2,10 @@ LEO_BIN=${1} -${LEO_BIN} --disable-update-check add --network credits.aleo --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} +${LEO_BIN} add --network credits.aleo --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} -${LEO_BIN} --disable-update-check build +${LEO_BIN} build -${LEO_BIN} --disable-update-check execute main 1u32 2u32 --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH --yes --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 +${LEO_BIN} execute main 1u32 2u32 --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH --yes --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 -${LEO_BIN} --disable-update-check execute main 1u32 2u32 --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH --yes --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 +${LEO_BIN} execute main 1u32 2u32 --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH --yes --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 diff --git a/tests/tests/cli/test_add_dependency/COMMANDS b/tests/tests/cli/test_add_dependency/COMMANDS index 4103de1f9c5..9382d3034a0 100755 --- a/tests/tests/cli/test_add_dependency/COMMANDS +++ b/tests/tests/cli/test_add_dependency/COMMANDS @@ -5,48 +5,48 @@ LEO_BIN=${1} cd my_program || exit 1 echo "--- 1. leo add with no source flag (should fail) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add some_dep +${LEO_BIN} add some_dep echo "no source exit code: $?" cat program.json echo "--- 2. leo add with multiple flags (should fail) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add some_dep --network --local ../some_path +${LEO_BIN} add some_dep --network --local ../some_path echo "multiple flags exit code: $?" cat program.json echo "--- 3. leo add --network (should succeed) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add --network credits --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} +${LEO_BIN} add --network credits --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} cat program.json echo "--- 4. leo add --local (should succeed) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add some_local --local ../some_local +${LEO_BIN} add some_local --local ../some_local cat program.json echo "--- 5. leo add --local (should succeed) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add my_lib --local ../my_lib +${LEO_BIN} add my_lib --local ../my_lib cat program.json echo "--- 6. leo add overwrite existing dep (should succeed) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add credits --local ../credits +${LEO_BIN} add credits --local ../credits cat program.json echo "--- 7. leo add --dev for a program not on the network (should fail) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add --dev --network dev_dep --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} +${LEO_BIN} add --dev --network dev_dep --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} echo "missing network dep exit code: $?" cat program.json echo "--- 8. leo add --dev for a program on the network (should succeed) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add --dev --network credits --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} +${LEO_BIN} add --dev --network credits --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} echo "network dev dep exit code: $?" cat program.json echo "--- 9. leo remove library dep by bare name (should succeed) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check remove my_lib +${LEO_BIN} remove my_lib cat program.json echo "--- 10. leo remove program dep by bare name (should succeed) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check remove some_local +${LEO_BIN} remove some_local cat program.json echo "--- 11. leo remove non-existent dep (should fail) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check remove nonexistent || true +${LEO_BIN} remove nonexistent || true diff --git a/tests/tests/cli/test_add_library_to_program/COMMANDS b/tests/tests/cli/test_add_library_to_program/COMMANDS index 1dc9e2af89d..4ae33d59abe 100755 --- a/tests/tests/cli/test_add_library_to_program/COMMANDS +++ b/tests/tests/cli/test_add_library_to_program/COMMANDS @@ -6,9 +6,9 @@ cd my_program || exit 1 echo "--- leo add actual_lib.aleo --local (should fail: dep is a library, not a program) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add actual_lib.aleo --local ../actual_lib +${LEO_BIN} add actual_lib.aleo --local ../actual_lib echo "add exit code: $?" echo "--- leo add actual_lib --local (should succeed: auto-detected as library) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add actual_lib --local ../actual_lib +${LEO_BIN} add actual_lib --local ../actual_lib diff --git a/tests/tests/cli/test_add_workspace/COMMANDS b/tests/tests/cli/test_add_workspace/COMMANDS index 6505df42a91..ff7648d30ef 100755 --- a/tests/tests/cli/test_add_workspace/COMMANDS +++ b/tests/tests/cli/test_add_workspace/COMMANDS @@ -5,15 +5,15 @@ LEO_BIN=${1} cd swap || exit 1 echo "--- 1. leo add --workspace token (should succeed) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add --workspace token +${LEO_BIN} add --workspace token cat program.json echo "--- 2. leo add --workspace --dev token (should succeed) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add --workspace --dev token +${LEO_BIN} add --workspace --dev token cat program.json echo "--- 3. leo add --workspace nonexistent (should fail) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add --workspace nonexistent || true +${LEO_BIN} add --workspace nonexistent || true echo "exit code: $?" echo "--- 4. leo add --workspace outside workspace (should fail) ---" | tee /dev/stderr @@ -37,6 +37,6 @@ program standalone.aleo { } SRC cd "${STANDALONE_DIR}" || exit 1 -${LEO_BIN} --disable-update-check add --workspace token || true +${LEO_BIN} add --workspace token || true echo "exit code: $?" rm -rf "${STANDALONE_DIR}" diff --git a/tests/tests/cli/test_ambiguous_package/COMMANDS b/tests/tests/cli/test_ambiguous_package/COMMANDS index c13a38983fb..aec7a542afc 100755 --- a/tests/tests/cli/test_ambiguous_package/COMMANDS +++ b/tests/tests/cli/test_ambiguous_package/COMMANDS @@ -6,5 +6,5 @@ cd ambiguous_pkg || exit 1 echo "--- leo build (should fail: both main.leo and lib.leo exist) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check build +${LEO_BIN} build echo "build exit code: $?" diff --git a/tests/tests/cli/test_command_shortcuts/COMMANDS b/tests/tests/cli/test_command_shortcuts/COMMANDS index 08c6d9cd66f..29928eccd9e 100755 --- a/tests/tests/cli/test_command_shortcuts/COMMANDS +++ b/tests/tests/cli/test_command_shortcuts/COMMANDS @@ -2,5 +2,5 @@ LEO_BIN=${1} -${LEO_BIN} --disable-update-check b -${LEO_BIN} --disable-update-check r main 1u32 2u32 +${LEO_BIN} b +${LEO_BIN} r main 1u32 2u32 diff --git a/tests/tests/cli/test_dependency_visible_to_tests/COMMANDS b/tests/tests/cli/test_dependency_visible_to_tests/COMMANDS index 45e52d48d38..1212c64daea 100755 --- a/tests/tests/cli/test_dependency_visible_to_tests/COMMANDS +++ b/tests/tests/cli/test_dependency_visible_to_tests/COMMANDS @@ -8,4 +8,4 @@ LEO_BIN=${1} cd my_app || exit 1 echo "--- leo test with a dependencies-only library named from a test ---" -${LEO_BIN} --disable-update-check test +${LEO_BIN} test diff --git a/tests/tests/cli/test_deploy/COMMANDS b/tests/tests/cli/test_deploy/COMMANDS index d7d5fe8cf54..56d8c26ffc2 100755 --- a/tests/tests/cli/test_deploy/COMMANDS +++ b/tests/tests/cli/test_deploy/COMMANDS @@ -7,10 +7,10 @@ LEO_BIN=${1} REMOTE=$(mktemp -d) trap 'rm -rf "$REMOTE"' EXIT PRIVATE_KEY="APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH" -COMMON_OPTS="--disable-update-check --broadcast --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" +COMMON_OPTS="--broadcast --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" ALEO_FILE="./build/some_sample_leo_program/some_sample_leo_program.aleo" -$LEO_BIN build --disable-update-check --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} +$LEO_BIN build --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} $LEO_BIN --path $ALEO_FILE deploy -y $COMMON_OPTS # Remote commands work from an empty directory without a supplied lock. mkdir "$REMOTE/empty" "$REMOTE/home" @@ -26,7 +26,7 @@ for command in run execute synthesize; do args=(synthesize some_sample_leo_program.aleo --skip mint --skip dynamic_transfer) ;; esac - if ! "$LEO_BIN" --disable-update-check --path "$REMOTE/empty" --home "$REMOTE/home" "${args[@]}" \ + if ! "$LEO_BIN" --path "$REMOTE/empty" --home "$REMOTE/home" "${args[@]}" \ --network testnet --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" > "$REMOTE/output" 2>&1; then cat "$REMOTE/output" exit 1 @@ -41,7 +41,7 @@ done # An extra remote program also needs no hand-written pin. sed 's/some_sample_leo_program/local_probe/g' "$ALEO_FILE" > "$REMOTE/empty/local_probe.aleo" -if ! "$LEO_BIN" --disable-update-check --path "$REMOTE/empty/local_probe.aleo" --home "$REMOTE/home" \ +if ! "$LEO_BIN" --path "$REMOTE/empty/local_probe.aleo" --home "$REMOTE/home" \ run main 1u32 2u32 --with some_sample_leo_program.aleo --network testnet \ --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" --private-key "$PRIVATE_KEY" > "$REMOTE/output" 2>&1; then cat "$REMOTE/output" @@ -53,8 +53,8 @@ test ! -e "$REMOTE/empty/leo.lock" echo "Remote run, execute, synthesize, and --with work without a supplied lock." # Keep an automatically generated dependency lock at edition zero for the upgrade check. -"$LEO_BIN" --disable-update-check --path "$REMOTE/lock_consumer" new lock_consumer > "$REMOTE/output" 2>&1 -if ! "$LEO_BIN" --disable-update-check --path "$REMOTE/lock_consumer" --home "$REMOTE/home" \ +"$LEO_BIN" --path "$REMOTE/lock_consumer" new lock_consumer > "$REMOTE/output" 2>&1 +if ! "$LEO_BIN" --path "$REMOTE/lock_consumer" --home "$REMOTE/home" \ add some_sample_leo_program --network --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" \ > "$REMOTE/output" 2>&1; then cat "$REMOTE/output" @@ -77,7 +77,7 @@ test "$(curl -fsS "http://localhost:${LEO_DEVNODE_PORT:-3030}/testnet/program/so cmp "$REMOTE/lock_consumer/leo.lock" "$REMOTE/original.lock" # A dry run reports the new edition without changing the lock or manifest. -if ! "$LEO_BIN" --disable-update-check --path "$REMOTE/lock_consumer" --home "$REMOTE/home" \ +if ! "$LEO_BIN" --path "$REMOTE/lock_consumer" --home "$REMOTE/home" \ update some_sample_leo_program --dry-run --network testnet --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" \ --network-retries 0 > "$REMOTE/output" 2>&1; then cat "$REMOTE/output" @@ -87,7 +87,7 @@ grep -Fq 'Would update some_sample_leo_program.aleo (testnet) edition 0 -> 2' "$ cmp "$REMOTE/lock_consumer/leo.lock" "$REMOTE/original.lock" cmp "$REMOTE/lock_consumer/program.json" "$REMOTE/original.json" -if ! "$LEO_BIN" --disable-update-check --path "$REMOTE/lock_consumer" --home "$REMOTE/home" \ +if ! "$LEO_BIN" --path "$REMOTE/lock_consumer" --home "$REMOTE/home" \ update some_sample_leo_program --network testnet --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" \ --network-retries 0 > "$REMOTE/output" 2>&1; then cat "$REMOTE/output" @@ -98,7 +98,7 @@ grep -Fq '"edition": 2' "$REMOTE/lock_consumer/leo.lock" cmp "$REMOTE/lock_consumer/program.json" "$REMOTE/original.json" # A manifest constraint keeps an exact edition fixed during a full update. -if ! "$LEO_BIN" --disable-update-check --path "$REMOTE/lock_consumer" --home "$REMOTE/home" \ +if ! "$LEO_BIN" --path "$REMOTE/lock_consumer" --home "$REMOTE/home" \ add some_sample_leo_program --edition 0 --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" \ > "$REMOTE/output" 2>&1; then cat "$REMOTE/output" @@ -107,7 +107,7 @@ fi grep -Fq '"edition": 0' "$REMOTE/lock_consumer/leo.lock" cp "$REMOTE/lock_consumer/leo.lock" "$REMOTE/exact.lock" cp "$REMOTE/lock_consumer/program.json" "$REMOTE/exact.json" -if ! "$LEO_BIN" --disable-update-check --path "$REMOTE/lock_consumer" --home "$REMOTE/home" \ +if ! "$LEO_BIN" --path "$REMOTE/lock_consumer" --home "$REMOTE/home" \ update --network testnet --endpoint "http://localhost:${LEO_DEVNODE_PORT:-3030}" \ --network-retries 0 > "$REMOTE/output" 2>&1; then cat "$REMOTE/output" diff --git a/tests/tests/cli/test_deploy_rename/COMMANDS b/tests/tests/cli/test_deploy_rename/COMMANDS index 4080c9ed57d..2f1bddaa200 100755 --- a/tests/tests/cli/test_deploy_rename/COMMANDS +++ b/tests/tests/cli/test_deploy_rename/COMMANDS @@ -4,14 +4,14 @@ LEO_BIN=${1} PRIVATE_KEY="APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH" ENDPOINT="http://localhost:${LEO_DEVNODE_PORT:-3030}" -COMMON_OPTS="--disable-update-check --network testnet --endpoint $ENDPOINT --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" +COMMON_OPTS="--network testnet --endpoint $ENDPOINT --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" # The package declares `original_prog.aleo`, but `--rename` recompiles and deploys # it on-chain under a different identity. $LEO_BIN deploy -y --broadcast --rename renamed_prog $COMMON_OPTS # Confirm the renamed program actually landed on-chain under its new name. -$LEO_BIN query --disable-update-check --network testnet --endpoint $ENDPOINT program renamed_prog.aleo +$LEO_BIN query --network testnet --endpoint $ENDPOINT program renamed_prog.aleo # Execute the renamed program from the original project directory. The local # package still declares `original_prog`, so the qualified name resolves to a diff --git a/tests/tests/cli/test_deploy_rename_conflict/COMMANDS b/tests/tests/cli/test_deploy_rename_conflict/COMMANDS index 4e28c013c2a..eb2b77dcd0b 100755 --- a/tests/tests/cli/test_deploy_rename_conflict/COMMANDS +++ b/tests/tests/cli/test_deploy_rename_conflict/COMMANDS @@ -2,7 +2,7 @@ LEO_BIN=${1} PRIVATE_KEY="APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH" -COMMON_OPTS="--disable-update-check --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" +COMMON_OPTS="--network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" # Renaming the primary program onto a dependency's name must be rejected before # any network interaction, so this test manages no devnode (see SKIP_DEVNODE). diff --git a/tests/tests/cli/test_deploy_rename_rejected/COMMANDS b/tests/tests/cli/test_deploy_rename_rejected/COMMANDS index 6e5c127424a..1c9df829bb0 100755 --- a/tests/tests/cli/test_deploy_rename_rejected/COMMANDS +++ b/tests/tests/cli/test_deploy_rename_rejected/COMMANDS @@ -2,7 +2,7 @@ LEO_BIN=${1} PRIVATE_KEY="APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH" -COMMON_OPTS="--disable-update-check --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" +COMMON_OPTS="--network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" # Both rejections fire before any network interaction, so this test manages no # devnode (see SKIP_DEVNODE). diff --git a/tests/tests/cli/test_deploy_rename_workspace_member/COMMANDS b/tests/tests/cli/test_deploy_rename_workspace_member/COMMANDS index f14215c29bb..c84f3acfaef 100755 --- a/tests/tests/cli/test_deploy_rename_workspace_member/COMMANDS +++ b/tests/tests/cli/test_deploy_rename_workspace_member/COMMANDS @@ -3,7 +3,7 @@ LEO_BIN=${1} PRIVATE_KEY="APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH" ENDPOINT="http://localhost:${LEO_DEVNODE_PORT:-3030}" -COMMON_OPTS="--disable-update-check --network testnet --endpoint $ENDPOINT --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" +COMMON_OPTS="--network testnet --endpoint $ENDPOINT --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" # Scoping the deploy to a single workspace member (here via `--package`) makes # `--rename` valid: the dependency-free `token` member is deployed on-chain as @@ -11,4 +11,4 @@ COMMON_OPTS="--disable-update-check --network testnet --endpoint $ENDPOINT --pri $LEO_BIN deploy -y --broadcast --package token --rename renamed_token $COMMON_OPTS # Confirm the renamed member landed on-chain under its new name. -$LEO_BIN query --disable-update-check --network testnet --endpoint $ENDPOINT program renamed_token.aleo +$LEO_BIN query --network testnet --endpoint $ENDPOINT program renamed_token.aleo diff --git a/tests/tests/cli/test_deploy_rename_workspace_rejected/COMMANDS b/tests/tests/cli/test_deploy_rename_workspace_rejected/COMMANDS index 9d3490fda2e..3341448004f 100755 --- a/tests/tests/cli/test_deploy_rename_workspace_rejected/COMMANDS +++ b/tests/tests/cli/test_deploy_rename_workspace_rejected/COMMANDS @@ -2,7 +2,7 @@ LEO_BIN=${1} PRIVATE_KEY="APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH" -COMMON_OPTS="--disable-update-check --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" +COMMON_OPTS="--network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" # A single `--rename` cannot apply across multiple workspace members. Deploying # the whole workspace (from the root) with `--rename` must be rejected before any diff --git a/tests/tests/cli/test_devnet/COMMANDS b/tests/tests/cli/test_devnet/COMMANDS index 02305df9d8c..308e3cb976e 100755 --- a/tests/tests/cli/test_devnet/COMMANDS +++ b/tests/tests/cli/test_devnet/COMMANDS @@ -20,7 +20,7 @@ cleanup() { } trap cleanup EXIT -$LEO_BIN --disable-update-check devnet \ +$LEO_BIN devnet \ --snarkos "$SNARKOS" \ --num-clients 0 \ --rest-port "$REST_PORT" \ @@ -92,5 +92,5 @@ kill "$PID" 2>/dev/null && wait "$PID" 2>/dev/null PID="" # Clean devnet storage and report. -$LEO_BIN --disable-update-check devnet --clean-only --snarkos "$SNARKOS" --storage "$STORAGE" --yes >/dev/null 2>&1 +$LEO_BIN devnet --clean-only --snarkos "$SNARKOS" --storage "$STORAGE" --yes >/dev/null 2>&1 echo "Leo devnet clean succeeded" diff --git a/tests/tests/cli/test_devnode/COMMANDS b/tests/tests/cli/test_devnode/COMMANDS index 4341790ac68..21284969188 100755 --- a/tests/tests/cli/test_devnode/COMMANDS +++ b/tests/tests/cli/test_devnode/COMMANDS @@ -15,7 +15,7 @@ cleanup() { trap cleanup EXIT # Start devnode on the allocated port. -$LEO_BIN --disable-update-check devnode start \ +$LEO_BIN devnode start \ --socket-addr "127.0.0.1:${PORT}" \ --private-key APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH \ >/dev/null 2>&1 & @@ -39,7 +39,7 @@ fi echo "Leo devnode started" # Advance the ledger by 5 blocks. -$LEO_BIN --disable-update-check devnode advance 5 \ +$LEO_BIN devnode advance 5 \ --socket-addr "127.0.0.1:${PORT}" \ >/dev/null 2>&1 diff --git a/tests/tests/cli/test_devnode_persistent_storage/COMMANDS b/tests/tests/cli/test_devnode_persistent_storage/COMMANDS index b5555d078b8..3c02a792002 100755 --- a/tests/tests/cli/test_devnode_persistent_storage/COMMANDS +++ b/tests/tests/cli/test_devnode_persistent_storage/COMMANDS @@ -35,7 +35,7 @@ wait_for_devnode() { } # ── Start devnode 1 with persistent storage ──────────────────────────────────── -$LEO_BIN --disable-update-check devnode start \ +$LEO_BIN devnode start \ --socket-addr "127.0.0.1:${PORT}" \ --private-key "$PRIVATE_KEY" \ --storage "$LEDGER_DIR" \ @@ -46,7 +46,7 @@ wait_for_devnode echo "--- Devnode 1 started ---" # ── Deploy and execute to write mapping state ────────────────────────────────── -COMMON_OPTS="--disable-update-check --broadcast --network testnet --endpoint http://localhost:${PORT} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" +COMMON_OPTS="--broadcast --network testnet --endpoint http://localhost:${PORT} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" echo "--- leo deploy ---" $LEO_BIN deploy -y $COMMON_OPTS @@ -87,7 +87,7 @@ while [ $SECONDS -lt $DEADLINE ]; do done # ── Start devnode 2 with the same persistent storage ────────────────────────── -$LEO_BIN --disable-update-check devnode start \ +$LEO_BIN devnode start \ --socket-addr "127.0.0.1:${PORT}" \ --private-key "$PRIVATE_KEY" \ --storage "$LEDGER_DIR" \ diff --git a/tests/tests/cli/test_devnode_view/COMMANDS b/tests/tests/cli/test_devnode_view/COMMANDS index d1bcbdc28b9..679b2d2a653 100755 --- a/tests/tests/cli/test_devnode_view/COMMANDS +++ b/tests/tests/cli/test_devnode_view/COMMANDS @@ -10,7 +10,7 @@ PROG="test_devnode_view.aleo" # Deploy the program carrying the `view fn` to the framework-managed devnode and # block until it is confirmed on-chain. -$LEO_BIN deploy -y --disable-update-check --broadcast --network testnet \ +$LEO_BIN deploy -y --broadcast --network testnet \ --endpoint $ENDPOINT --private-key $PRIVATE_KEY \ --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 >/dev/null 2>&1 diff --git a/tests/tests/cli/test_duplicate_lib_name/COMMANDS b/tests/tests/cli/test_duplicate_lib_name/COMMANDS index 48c28f9e02b..6966d5e1799 100755 --- a/tests/tests/cli/test_duplicate_lib_name/COMMANDS +++ b/tests/tests/cli/test_duplicate_lib_name/COMMANDS @@ -5,5 +5,5 @@ LEO_BIN=${1} echo "--- leo build main (should fail: conflicting library name 'helper') ---" | tee /dev/stderr cd main || exit 1 -${LEO_BIN} --disable-update-check build +${LEO_BIN} build echo "build exit code: $?" diff --git a/tests/tests/cli/test_dynamic_call_with_flag/COMMANDS b/tests/tests/cli/test_dynamic_call_with_flag/COMMANDS index 001887aab73..4ce92330f40 100755 --- a/tests/tests/cli/test_dynamic_call_with_flag/COMMANDS +++ b/tests/tests/cli/test_dynamic_call_with_flag/COMMANDS @@ -1,7 +1,6 @@ #!/usr/bin/env bash LEO_BIN=${1} -COMMON_OPTS="--disable-update-check" PROG="'extra_prog'" NET="'aleo'" @@ -9,16 +8,16 @@ FUNC="'dbl'" INPUT="7u64" echo "--- run WITHOUT --with should fail ---" -${LEO_BIN} ${COMMON_OPTS} run main ${PROG} ${NET} ${FUNC} ${INPUT} 2>&1 || true +${LEO_BIN} run main ${PROG} ${NET} ${FUNC} ${INPUT} 2>&1 || true echo "--- run WITH --with should succeed ---" -${LEO_BIN} ${COMMON_OPTS} run main ${PROG} ${NET} ${FUNC} ${INPUT} --with ./extra_prog.aleo +${LEO_BIN} run main ${PROG} ${NET} ${FUNC} ${INPUT} --with ./extra_prog.aleo echo "--- run a standalone Aleo file ---" -${LEO_BIN} ${COMMON_OPTS} --path ./extra_prog.aleo run dbl ${INPUT} +${LEO_BIN} --path ./extra_prog.aleo run dbl ${INPUT} echo "--- write standalone Aleo run JSON output ---" -${LEO_BIN} ${COMMON_OPTS} --path ./extra_prog.aleo --json-output run dbl ${INPUT} +${LEO_BIN} --path ./extra_prog.aleo --json-output run dbl ${INPUT} cat ./build/json-outputs/run.json rm ./build/json-outputs/run.json diff --git a/tests/tests/cli/test_execute_view_rejected/COMMANDS b/tests/tests/cli/test_execute_view_rejected/COMMANDS index 8934b260918..92188f09659 100755 --- a/tests/tests/cli/test_execute_view_rejected/COMMANDS +++ b/tests/tests/cli/test_execute_view_rejected/COMMANDS @@ -12,6 +12,6 @@ PRIVATE_KEY="APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH" ENDPOINT="http://127.0.0.1:1" echo "--- execute on a view fn should fail with a clear message ---" -${LEO_BIN} --disable-update-check execute --network testnet --endpoint $ENDPOINT --private-key $PRIVATE_KEY get_balance aleo1rhgdu77hgyqd3xjj8ucu3jj9r2krwz6mnzyd80gncr5fxcwlh5rsvzp9px 2>&1 || true +${LEO_BIN} execute --network testnet --endpoint $ENDPOINT --private-key $PRIVATE_KEY get_balance aleo1rhgdu77hgyqd3xjj8ucu3jj9r2krwz6mnzyd80gncr5fxcwlh5rsvzp9px 2>&1 || true exit 0 diff --git a/tests/tests/cli/test_external_constructor_ctx/COMMANDS b/tests/tests/cli/test_external_constructor_ctx/COMMANDS index 977598728c8..15039db3336 100755 --- a/tests/tests/cli/test_external_constructor_ctx/COMMANDS +++ b/tests/tests/cli/test_external_constructor_ctx/COMMANDS @@ -7,9 +7,9 @@ LEO_BIN=${1} cd child || exit 1 echo "--- leo build child ---" -${LEO_BIN} --disable-update-check build --checksums +${LEO_BIN} build --checksums cd ../parent || exit 1 echo "--- leo build parent ---" -${LEO_BIN} --disable-update-check build --checksums +${LEO_BIN} build --checksums diff --git a/tests/tests/cli/test_external_program_submodule/COMMANDS b/tests/tests/cli/test_external_program_submodule/COMMANDS index e25169f0edf..c65ac6b026c 100755 --- a/tests/tests/cli/test_external_program_submodule/COMMANDS +++ b/tests/tests/cli/test_external_program_submodule/COMMANDS @@ -8,4 +8,4 @@ LEO_BIN=${1} cd consumer || exit 1 echo "--- leo build (access provider.aleo submodule items) ---" -${LEO_BIN} --disable-update-check build --checksums +${LEO_BIN} build --checksums diff --git a/tests/tests/cli/test_external_toplevel_closure/COMMANDS b/tests/tests/cli/test_external_toplevel_closure/COMMANDS index 76385710574..e953704c68e 100755 --- a/tests/tests/cli/test_external_toplevel_closure/COMMANDS +++ b/tests/tests/cli/test_external_toplevel_closure/COMMANDS @@ -10,4 +10,4 @@ LEO_BIN=${1} cd parent || exit 1 echo "--- leo build (call child.aleo top-level closure) ---" -${LEO_BIN} --disable-update-check build +${LEO_BIN} build diff --git a/tests/tests/cli/test_failure_exit_code/COMMANDS b/tests/tests/cli/test_failure_exit_code/COMMANDS index 0ce05e10a73..2ee615b7deb 100755 --- a/tests/tests/cli/test_failure_exit_code/COMMANDS +++ b/tests/tests/cli/test_failure_exit_code/COMMANDS @@ -2,4 +2,4 @@ LEO_BIN=${1} -${LEO_BIN} --disable-update-check test +${LEO_BIN} test diff --git a/tests/tests/cli/test_fmt/COMMANDS b/tests/tests/cli/test_fmt/COMMANDS index 29a16761ce3..93629ec91e3 100755 --- a/tests/tests/cli/test_fmt/COMMANDS +++ b/tests/tests/cli/test_fmt/COMMANDS @@ -3,12 +3,12 @@ LEO_BIN=${1} echo "--- fmt program ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check fmt --check || true -${LEO_BIN} --disable-update-check fmt -${LEO_BIN} --disable-update-check fmt --check +${LEO_BIN} fmt --check || true +${LEO_BIN} fmt +${LEO_BIN} fmt --check echo "--- fmt library ---" | tee /dev/stderr cd ugly_lib || exit 1 -${LEO_BIN} --disable-update-check fmt --check || true -${LEO_BIN} --disable-update-check fmt -${LEO_BIN} --disable-update-check fmt --check +${LEO_BIN} fmt --check || true +${LEO_BIN} fmt +${LEO_BIN} fmt --check diff --git a/tests/tests/cli/test_git_dependency/COMMANDS b/tests/tests/cli/test_git_dependency/COMMANDS index ac467c7b523..f821343f17c 100755 --- a/tests/tests/cli/test_git_dependency/COMMANDS +++ b/tests/tests/cli/test_git_dependency/COMMANDS @@ -29,7 +29,7 @@ printf 'program gitprog.aleo {\n @noupgrade\n constructor() {}\n fn run # An Aleo bytecode file at the root: compile a throwaway program so the bytecode is valid for the # current compiler, then place its `.aleo` at the repository root. GEN=$(mktemp -d) -( cd "${GEN}" && ${LEO_BIN} --disable-update-check --home "${HOMEDIR}" new gitbytes > /dev/null 2>&1 && cd gitbytes && ${LEO_BIN} --disable-update-check --home "${HOMEDIR}" build > /dev/null 2>&1 ) +( cd "${GEN}" && ${LEO_BIN} --home "${HOMEDIR}" new gitbytes > /dev/null 2>&1 && cd gitbytes && ${LEO_BIN} --home "${HOMEDIR}" build > /dev/null 2>&1 ) cp "$(find "${GEN}/gitbytes/build" -name 'gitbytes.aleo' | head -1)" "${REPO}/gitbytes.aleo" ${GIT} -C "${REPO}" init -q @@ -51,7 +51,7 @@ OUT=$(mktemp) ERR=$(mktemp) # Run `leo`, redacting the git URL/commits from both its stdout and stderr while preserving order. leo_redacted() { - ${LEO_BIN} --disable-update-check --home "${HOMEDIR}" "$@" >"${OUT}" 2>"${ERR}" + ${LEO_BIN} --home "${HOMEDIR}" "$@" >"${OUT}" 2>"${ERR}" local rc=$? redact <"${OUT}" redact <"${ERR}" >&2 diff --git a/tests/tests/cli/test_identifier_input/COMMANDS b/tests/tests/cli/test_identifier_input/COMMANDS index 9f2b8b6e42b..39b7d714396 100755 --- a/tests/tests/cli/test_identifier_input/COMMANDS +++ b/tests/tests/cli/test_identifier_input/COMMANDS @@ -2,10 +2,10 @@ LEO_BIN=${1} PRIVATE_KEY="APrivateKey1zkp2RWGDcde3efb89rjhME1VYA8QMxcxep5DShNBR6n8Yjh" -COMMON_OPTS="--disable-update-check --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" +COMMON_OPTS="--network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" echo "--- leo run with identifier input ---" -${LEO_BIN} --disable-update-check run main "'hello'" +${LEO_BIN} run main "'hello'" echo "--- leo execute with identifier input ---" ${LEO_BIN} execute -y $COMMON_OPTS main "'hello'" diff --git a/tests/tests/cli/test_interface_abi_from_library/COMMANDS b/tests/tests/cli/test_interface_abi_from_library/COMMANDS index 789f94721f4..5baafe1479f 100755 --- a/tests/tests/cli/test_interface_abi_from_library/COMMANDS +++ b/tests/tests/cli/test_interface_abi_from_library/COMMANDS @@ -7,10 +7,10 @@ LEO_BIN=${1} cd my_program || exit 1 echo "--- leo add --local (add lib dep to program) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add my_lib --local ../my_lib +${LEO_BIN} add my_lib --local ../my_lib echo "--- leo build ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check build +${LEO_BIN} build echo "build exit code: $?" echo "--- program ABI ---" | tee /dev/stderr diff --git a/tests/tests/cli/test_interface_abi_inheritance/COMMANDS b/tests/tests/cli/test_interface_abi_inheritance/COMMANDS index 867e63ae068..11c4559aece 100755 --- a/tests/tests/cli/test_interface_abi_inheritance/COMMANDS +++ b/tests/tests/cli/test_interface_abi_inheritance/COMMANDS @@ -6,7 +6,7 @@ LEO_BIN=${1} echo "--- leo build (interface inheritance) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check build +${LEO_BIN} build echo "build exit code: $?" echo "--- program ABI ---" | tee /dev/stderr diff --git a/tests/tests/cli/test_interface_abi_local/COMMANDS b/tests/tests/cli/test_interface_abi_local/COMMANDS index 96383eec5ea..d3dfdad5935 100755 --- a/tests/tests/cli/test_interface_abi_local/COMMANDS +++ b/tests/tests/cli/test_interface_abi_local/COMMANDS @@ -7,7 +7,7 @@ LEO_BIN=${1} echo "--- leo build (program with local interfaces) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check build +${LEO_BIN} build echo "build exit code: $?" echo "--- program ABI ---" | tee /dev/stderr diff --git a/tests/tests/cli/test_invalid_literal_rejection/COMMANDS b/tests/tests/cli/test_invalid_literal_rejection/COMMANDS index 4f3d0a1cd04..9ae582a5963 100755 --- a/tests/tests/cli/test_invalid_literal_rejection/COMMANDS +++ b/tests/tests/cli/test_invalid_literal_rejection/COMMANDS @@ -1,22 +1,21 @@ #!/usr/bin/env bash LEO_BIN=${1} -COMMON_OPTS="--disable-update-check" echo "--- valid field literal should succeed ---" | tee /dev/stderr -${LEO_BIN} ${COMMON_OPTS} run main 42field +${LEO_BIN} run main 42field echo "--- truefield should be rejected ---" | tee /dev/stderr -${LEO_BIN} ${COMMON_OPTS} run main truefield +${LEO_BIN} run main truefield echo "--- empty prefix field should be rejected ---" | tee /dev/stderr -${LEO_BIN} ${COMMON_OPTS} run main field +${LEO_BIN} run main field echo "--- falsescalar should be rejected ---" | tee /dev/stderr -${LEO_BIN} ${COMMON_OPTS} run main falsescalar +${LEO_BIN} run main falsescalar echo "--- abcu64 should be rejected ---" | tee /dev/stderr -${LEO_BIN} ${COMMON_OPTS} run main abcu64 +${LEO_BIN} run main abcu64 # Exit 0 so the test framework sees a clean exit; the assertions # are captured via STDOUT/STDERR comparison. diff --git a/tests/tests/cli/test_json/COMMANDS b/tests/tests/cli/test_json/COMMANDS index fe1038d7f70..31df715b6b7 100755 --- a/tests/tests/cli/test_json/COMMANDS +++ b/tests/tests/cli/test_json/COMMANDS @@ -3,13 +3,13 @@ LEO_BIN=${1} PRIVATE_KEY="APrivateKey1zkp2RWGDcde3efb89rjhME1VYA8QMxcxep5DShNBR6n8Yjh" -COMMON_OPTS="--disable-update-check --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" +COMMON_OPTS="--network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" # `synthesize` and `query` don't sign or set consensus, so they only take the connection options. -CONN_OPTS="--disable-update-check --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030}" +CONN_OPTS="--network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030}" -$LEO_BIN --disable-update-check run --json-output main 1u32 2u32 +$LEO_BIN run --json-output main 1u32 2u32 -$LEO_BIN --disable-update-check test --json-output +$LEO_BIN test --json-output $LEO_BIN synthesize --json-output --local test_json_program.aleo $CONN_OPTS @@ -21,4 +21,4 @@ $LEO_BIN query --json-output program test_json_program.aleo $CONN_OPTS $LEO_BIN upgrade -y --json-output --broadcast $COMMON_OPTS -$LEO_BIN --disable-update-check build --json-output --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} +$LEO_BIN build --json-output --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} diff --git a/tests/tests/cli/test_lib_program_test/COMMANDS b/tests/tests/cli/test_lib_program_test/COMMANDS index dff986d52f8..6838d61271b 100755 --- a/tests/tests/cli/test_lib_program_test/COMMANDS +++ b/tests/tests/cli/test_lib_program_test/COMMANDS @@ -14,4 +14,4 @@ LEO_BIN=${1} cd my_program || exit 1 echo "--- leo test (should succeed) ---" -${LEO_BIN} --disable-update-check test +${LEO_BIN} test diff --git a/tests/tests/cli/test_lib_submodule_with_tests/COMMANDS b/tests/tests/cli/test_lib_submodule_with_tests/COMMANDS index be5a01056d6..9a067ef3250 100755 --- a/tests/tests/cli/test_lib_submodule_with_tests/COMMANDS +++ b/tests/tests/cli/test_lib_submodule_with_tests/COMMANDS @@ -8,4 +8,4 @@ LEO_BIN=${1} cd my_lib || exit 1 echo "--- leo test on a library with submodules ---" -${LEO_BIN} --disable-update-check test +${LEO_BIN} test diff --git a/tests/tests/cli/test_lib_with_tests/COMMANDS b/tests/tests/cli/test_lib_with_tests/COMMANDS index 7ac13aca8d1..f5821b7ad38 100755 --- a/tests/tests/cli/test_lib_with_tests/COMMANDS +++ b/tests/tests/cli/test_lib_with_tests/COMMANDS @@ -8,4 +8,4 @@ LEO_BIN=${1} cd my_lib || exit 1 echo "--- leo test on a library with tests/ ---" -${LEO_BIN} --disable-update-check test +${LEO_BIN} test diff --git a/tests/tests/cli/test_library/COMMANDS b/tests/tests/cli/test_library/COMMANDS index 209c209725f..694e0f48884 100755 --- a/tests/tests/cli/test_library/COMMANDS +++ b/tests/tests/cli/test_library/COMMANDS @@ -5,40 +5,40 @@ LEO_BIN=${1} cd top_lib || exit 1 echo "--- leo add --local (should succeed) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add base_lib --local ../base_lib +${LEO_BIN} add base_lib --local ../base_lib echo "--- leo add --network on a library (should fail) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add credits --network +${LEO_BIN} add credits --network echo "add network to library exit code: $?" echo "--- leo build (should succeed) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check build +${LEO_BIN} build echo "--- leo run (should fail) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check run main +${LEO_BIN} run main echo "run exit code: $?" echo "--- leo execute (should fail) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check execute main --network testnet --endpoint http://localhost:3030 --private-key APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH --yes +${LEO_BIN} execute main --network testnet --endpoint http://localhost:3030 --private-key APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH --yes echo "execute exit code: $?" echo "--- leo deploy (should fail) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check deploy --network testnet --endpoint http://localhost:3030 --private-key APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH --yes +${LEO_BIN} deploy --network testnet --endpoint http://localhost:3030 --private-key APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH --yes echo "deploy exit code: $?" echo "--- leo test (should fail) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check test +${LEO_BIN} test echo "test exit code: $?" echo "--- leo upgrade (should fail) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check upgrade --network testnet +${LEO_BIN} upgrade --network testnet echo "upgrade exit code: $?" echo "--- leo clean on library (should succeed) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check clean +${LEO_BIN} clean echo "clean exit code: $?" echo "--- leo run with library dep (should succeed) ---" | tee /dev/stderr cd ../program_using_lib || exit 1 -${LEO_BIN} --disable-update-check run main 1u32 -${LEO_BIN} --disable-update-check clean +${LEO_BIN} run main 1u32 +${LEO_BIN} clean diff --git a/tests/tests/cli/test_library_build/COMMANDS b/tests/tests/cli/test_library_build/COMMANDS index 6009b728752..7fba5626554 100755 --- a/tests/tests/cli/test_library_build/COMMANDS +++ b/tests/tests/cli/test_library_build/COMMANDS @@ -5,11 +5,11 @@ LEO_BIN=${1} echo "--- leo add --local (add lib dep to program) ---" | tee /dev/stderr cd my_program || exit 1 -${LEO_BIN} --disable-update-check add my_lib --local ../my_lib +${LEO_BIN} add my_lib --local ../my_lib echo "--- leo build (should succeed) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check build +${LEO_BIN} build echo "--- bytecode (should NOT contain 'import my_lib') ---" | tee /dev/stderr diff --git a/tests/tests/cli/test_library_build_interface_cycle/COMMANDS b/tests/tests/cli/test_library_build_interface_cycle/COMMANDS index 53e472cce8c..f1cba948862 100755 --- a/tests/tests/cli/test_library_build_interface_cycle/COMMANDS +++ b/tests/tests/cli/test_library_build_interface_cycle/COMMANDS @@ -8,5 +8,5 @@ LEO_BIN=${1} cd my_lib || exit 1 echo "--- leo build (should fail: cyclic interfaces) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check build +${LEO_BIN} build echo "build exit code: $?" diff --git a/tests/tests/cli/test_library_build_type_error/COMMANDS b/tests/tests/cli/test_library_build_type_error/COMMANDS index 693dc44e368..0602d9b171b 100755 --- a/tests/tests/cli/test_library_build_type_error/COMMANDS +++ b/tests/tests/cli/test_library_build_type_error/COMMANDS @@ -8,5 +8,5 @@ LEO_BIN=${1} cd my_lib || exit 1 echo "--- leo build (should fail: type error in library) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check build +${LEO_BIN} build echo "build exit code: $?" diff --git a/tests/tests/cli/test_library_build_undefined_name/COMMANDS b/tests/tests/cli/test_library_build_undefined_name/COMMANDS index 2b1ef7ca112..8661bf1846c 100755 --- a/tests/tests/cli/test_library_build_undefined_name/COMMANDS +++ b/tests/tests/cli/test_library_build_undefined_name/COMMANDS @@ -8,5 +8,5 @@ LEO_BIN=${1} cd my_lib || exit 1 echo "--- leo build (should fail: undefined name in library) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check build +${LEO_BIN} build echo "build exit code: $?" diff --git a/tests/tests/cli/test_library_build_valid_interface/COMMANDS b/tests/tests/cli/test_library_build_valid_interface/COMMANDS index 7b3845cb873..1d87cee0abc 100755 --- a/tests/tests/cli/test_library_build_valid_interface/COMMANDS +++ b/tests/tests/cli/test_library_build_valid_interface/COMMANDS @@ -8,5 +8,5 @@ LEO_BIN=${1} cd my_lib || exit 1 echo "--- leo build (should succeed: valid interface library) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check build +${LEO_BIN} build echo "build exit code: $?" diff --git a/tests/tests/cli/test_library_build_with_dep_lib/COMMANDS b/tests/tests/cli/test_library_build_with_dep_lib/COMMANDS index c3751ec3c37..660f572ebd4 100755 --- a/tests/tests/cli/test_library_build_with_dep_lib/COMMANDS +++ b/tests/tests/cli/test_library_build_with_dep_lib/COMMANDS @@ -9,8 +9,8 @@ LEO_BIN=${1} cd top_lib || exit 1 echo "--- leo add --local base_lib (should succeed) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add base_lib --local ../base_lib +${LEO_BIN} add base_lib --local ../base_lib echo "--- leo build (should succeed: library-to-library reference) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check build +${LEO_BIN} build echo "build exit code: $?" diff --git a/tests/tests/cli/test_library_circular/COMMANDS b/tests/tests/cli/test_library_circular/COMMANDS index 297abfb7451..e0e54db53d2 100755 --- a/tests/tests/cli/test_library_circular/COMMANDS +++ b/tests/tests/cli/test_library_circular/COMMANDS @@ -5,15 +5,15 @@ LEO_BIN=${1} echo "--- add lib_b as dependency of lib_a ---" | tee /dev/stderr cd lib_a || exit 1 -${LEO_BIN} --disable-update-check add lib_b --local ../lib_b +${LEO_BIN} add lib_b --local ../lib_b echo "--- add lib_a as dependency of lib_b (creates cycle) ---" | tee /dev/stderr cd ../lib_b || exit 1 -${LEO_BIN} --disable-update-check add lib_a --local ../lib_a +${LEO_BIN} add lib_a --local ../lib_a echo "--- leo build lib_a (should fail: circular dependency) ---" | tee /dev/stderr cd ../lib_a || exit 1 -${LEO_BIN} --disable-update-check build +${LEO_BIN} build echo "build exit code: $?" diff --git a/tests/tests/cli/test_library_parse_error/COMMANDS b/tests/tests/cli/test_library_parse_error/COMMANDS index 80c8a6b15cc..48ac0f78554 100755 --- a/tests/tests/cli/test_library_parse_error/COMMANDS +++ b/tests/tests/cli/test_library_parse_error/COMMANDS @@ -6,9 +6,9 @@ cd my_program || exit 1 echo "--- leo add --local bad_lib ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check add bad_lib --local ../bad_lib +${LEO_BIN} add bad_lib --local ../bad_lib echo "--- leo build (should fail: library has parse error) ---" | tee /dev/stderr -${LEO_BIN} --disable-update-check build +${LEO_BIN} build echo "build exit code: $?" diff --git a/tests/tests/cli/test_library_submodule_access/COMMANDS b/tests/tests/cli/test_library_submodule_access/COMMANDS index 6c4e54906b2..199f952cb3d 100755 --- a/tests/tests/cli/test_library_submodule_access/COMMANDS +++ b/tests/tests/cli/test_library_submodule_access/COMMANDS @@ -7,7 +7,7 @@ LEO_BIN=${1} echo "--- leo add --local (add shapes_lib dep to my_app) ---" cd my_app || exit 1 -${LEO_BIN} --disable-update-check add shapes_lib --local ../shapes_lib +${LEO_BIN} add shapes_lib --local ../shapes_lib echo "--- leo test (should pass all submodule assertions) ---" -${LEO_BIN} --disable-update-check test +${LEO_BIN} test diff --git a/tests/tests/cli/test_multiple_test_files/COMMANDS b/tests/tests/cli/test_multiple_test_files/COMMANDS index 0ce05e10a73..2ee615b7deb 100755 --- a/tests/tests/cli/test_multiple_test_files/COMMANDS +++ b/tests/tests/cli/test_multiple_test_files/COMMANDS @@ -2,4 +2,4 @@ LEO_BIN=${1} -${LEO_BIN} --disable-update-check test +${LEO_BIN} test diff --git a/tests/tests/cli/test_network_retries/COMMANDS b/tests/tests/cli/test_network_retries/COMMANDS index b4e13319cd0..de9612c5fa6 100755 --- a/tests/tests/cli/test_network_retries/COMMANDS +++ b/tests/tests/cli/test_network_retries/COMMANDS @@ -19,7 +19,7 @@ LEO_BIN=${1} DEAD_ENDPOINT="http://localhost:1" # One retry: expect exactly one "retrying" message in stderr. -STDERR1=$(${LEO_BIN} --disable-update-check query \ +STDERR1=$(${LEO_BIN} query \ --network testnet \ --endpoint "${DEAD_ENDPOINT}" \ --network-retries 1 \ @@ -28,7 +28,7 @@ COUNT1=$(echo "${STDERR1}" | grep -c "Network request failed, retrying" || true) echo "retries=1: retry_count=${COUNT1}" # Zero retries: expect no "retrying" message. -STDERR0=$(${LEO_BIN} --disable-update-check query \ +STDERR0=$(${LEO_BIN} query \ --network testnet \ --endpoint "${DEAD_ENDPOINT}" \ --network-retries 0 \ diff --git a/tests/tests/cli/test_network_retries_flag/COMMANDS b/tests/tests/cli/test_network_retries_flag/COMMANDS index 71f1056e95b..a7e1dc0be6d 100755 --- a/tests/tests/cli/test_network_retries_flag/COMMANDS +++ b/tests/tests/cli/test_network_retries_flag/COMMANDS @@ -6,7 +6,7 @@ LEO_BIN=${1} for cmd in build run execute deploy upgrade query synthesize; do - if ${LEO_BIN} --disable-update-check "${cmd}" --help 2>&1 | grep -qF -- '--network-retries'; then + if ${LEO_BIN} "${cmd}" --help 2>&1 | grep -qF -- '--network-retries'; then echo "${cmd}: --network-retries OK" else echo "${cmd}: --network-retries MISSING" diff --git a/tests/tests/cli/test_run_view_rejected/COMMANDS b/tests/tests/cli/test_run_view_rejected/COMMANDS index 293b3697898..0b5244dc0c4 100755 --- a/tests/tests/cli/test_run_view_rejected/COMMANDS +++ b/tests/tests/cli/test_run_view_rejected/COMMANDS @@ -5,12 +5,11 @@ # only checks the functions IndexMap, not views). LEO_BIN=${1} -COMMON_OPTS="--disable-update-check" echo "--- run on a view fn should fail with a clear message ---" -${LEO_BIN} ${COMMON_OPTS} run get_balance aleo1rhgdu77hgyqd3xjj8ucu3jj9r2krwz6mnzyd80gncr5fxcwlh5rsvzp9px 2>&1 || true +${LEO_BIN} run get_balance aleo1rhgdu77hgyqd3xjj8ucu3jj9r2krwz6mnzyd80gncr5fxcwlh5rsvzp9px 2>&1 || true echo "--- run on a regular fn should still work ---" -${LEO_BIN} ${COMMON_OPTS} run noop 1u32 2>&1 | tail -n 5 +${LEO_BIN} run noop 1u32 2>&1 | tail -n 5 exit 0 diff --git a/tests/tests/cli/test_simple_build/COMMANDS b/tests/tests/cli/test_simple_build/COMMANDS index 61759328265..5023dfe9f51 100755 --- a/tests/tests/cli/test_simple_build/COMMANDS +++ b/tests/tests/cli/test_simple_build/COMMANDS @@ -2,4 +2,4 @@ LEO_BIN=${1} -${LEO_BIN} --disable-update-check build --checksums +${LEO_BIN} build --checksums diff --git a/tests/tests/cli/test_simple_test/COMMANDS b/tests/tests/cli/test_simple_test/COMMANDS index 0ce05e10a73..2ee615b7deb 100755 --- a/tests/tests/cli/test_simple_test/COMMANDS +++ b/tests/tests/cli/test_simple_test/COMMANDS @@ -2,4 +2,4 @@ LEO_BIN=${1} -${LEO_BIN} --disable-update-check test +${LEO_BIN} test diff --git a/tests/tests/cli/test_storage_from_unit_test/COMMANDS b/tests/tests/cli/test_storage_from_unit_test/COMMANDS index c83fe3399ab..1d4af54f337 100755 --- a/tests/tests/cli/test_storage_from_unit_test/COMMANDS +++ b/tests/tests/cli/test_storage_from_unit_test/COMMANDS @@ -6,4 +6,4 @@ LEO_BIN=${1} -${LEO_BIN} --disable-update-check test +${LEO_BIN} test diff --git a/tests/tests/cli/test_version/COMMANDS b/tests/tests/cli/test_version/COMMANDS index aed85fce940..1300ca22727 100755 --- a/tests/tests/cli/test_version/COMMANDS +++ b/tests/tests/cli/test_version/COMMANDS @@ -2,4 +2,4 @@ LEO_BIN=${1} -${LEO_BIN} --disable-update-check --version +${LEO_BIN} --version diff --git a/tests/tests/cli/test_with_record/COMMANDS b/tests/tests/cli/test_with_record/COMMANDS index 81cc6e9b13d..19cecec4478 100755 --- a/tests/tests/cli/test_with_record/COMMANDS +++ b/tests/tests/cli/test_with_record/COMMANDS @@ -13,4 +13,4 @@ LEO_BIN=${1} cd my_program || exit 1 echo "--- leo test (program with record, should succeed) ---" -${LEO_BIN} --disable-update-check test +${LEO_BIN} test diff --git a/tests/tests/cli/test_workspace_deploy/COMMANDS b/tests/tests/cli/test_workspace_deploy/COMMANDS index 6c46525fb45..40d504259ed 100755 --- a/tests/tests/cli/test_workspace_deploy/COMMANDS +++ b/tests/tests/cli/test_workspace_deploy/COMMANDS @@ -2,6 +2,6 @@ LEO_BIN=${1} PRIVATE_KEY="APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH" -COMMON_OPTS="--disable-update-check --broadcast --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" +COMMON_OPTS="--broadcast --network testnet --endpoint http://localhost:${LEO_DEVNODE_PORT:-3030} --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20" $LEO_BIN deploy -y $COMMON_OPTS From 8e8f755adc637b88a5f83df4e67b6f3a63b534b9 Mon Sep 17 00:00:00 2001 From: Mohammad Fawaz Date: Wed, 30 Sep 2026 19:37:17 -0400 Subject: [PATCH 09/10] Fix registry initialization after removing release checks --- crates/leo/src/cli/cli.rs | 37 ++++++++++++++++++++++ crates/leo/src/cli/commands/common/util.rs | 2 +- crates/leo/src/cli/helpers/context.rs | 9 +++--- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/crates/leo/src/cli/cli.rs b/crates/leo/src/cli/cli.rs index 7201c3688b2..a4c53f9c538 100644 --- a/crates/leo/src/cli/cli.rs +++ b/crates/leo/src/cli/cli.rs @@ -348,6 +348,43 @@ mod tests { } } + #[test] + #[serial] + fn build_initializes_missing_registry_directory() { + let root = tempfile::tempdir().expect("Create the fixture directory"); + test_helpers::scaffold_minimal_member(root.path(), "fresh_registry"); + let project = root.path().join("fresh_registry"); + std::fs::write( + project.join("src/main.leo"), + "program fresh_registry.aleo { fn main(public a: u32) -> u32 { return a; } @noupgrade constructor() {} }", + ) + .expect("Write a program with an entry point"); + let home = root.path().join("missing/home"); + assert!(!home.exists()); + let command = CLI::try_parse_from([ + "leo", + "-q", + "--path", + project.to_str().expect("The project path must be UTF-8"), + "--home", + home.to_str().expect("The registry path must be UTF-8"), + "build", + "--network", + "testnet", + ]) + .expect("The build arguments must parse"); + create_session_if_not_set_then(|_| { + run_with_args(command).expect("The first build must create its registry directory"); + }); + assert!(home.is_dir()); + assert!(project.join("build/fresh_registry/fresh_registry.aleo").is_file()); + + let file = root.path().join("not_a_directory"); + std::fs::write(&file, "file").expect("Create the invalid registry path"); + let context = crate::cli::context::Context::new(None, Some(file), false, None).expect("Create context"); + assert!(context.home().is_err(), "A registry path must be a directory"); + } + #[test] fn network_pins_use_the_workspace_root() { use crate::cli::context::Context; diff --git a/crates/leo/src/cli/commands/common/util.rs b/crates/leo/src/cli/commands/common/util.rs index b08a040e7e2..dbc9a5859a7 100644 --- a/crates/leo/src/cli/commands/common/util.rs +++ b/crates/leo/src/cli/commands/common/util.rs @@ -183,7 +183,7 @@ mod tests { .expect_err("An unreachable extra program must fail"); assert!(error.to_string().contains("failed to retrieve"), "{error}"); }); - assert!(!root.join("cache").exists()); + assert!(std::fs::read_dir(root.join("cache")).expect("The registry directory must exist").next().is_none()); std::fs::remove_dir_all(root).expect("The temporary directory must be removed"); } diff --git a/crates/leo/src/cli/helpers/context.rs b/crates/leo/src/cli/helpers/context.rs index d401ca629c0..7f0000d77a4 100644 --- a/crates/leo/src/cli/helpers/context.rs +++ b/crates/leo/src/cli/helpers/context.rs @@ -67,12 +67,11 @@ impl Context { } } - /// Returns the path to the Aleo registry directory. + /// Returns the path to the Aleo registry directory, creating it if needed. pub fn home(&self) -> Result { - match &self.home { - Some(path) => Ok(path.clone()), - None => Ok(aleo_dir()), - } + let path = self.home.clone().unwrap_or_else(aleo_dir); + std::fs::create_dir_all(&path).map_err(crate::errors::cli_io_error)?; + Ok(path) } /// Read dependency pins from the enclosing project or workspace. From 11c5ba588c36c75949074e748937a523d7e19b5d Mon Sep 17 00:00:00 2001 From: Mohammad Fawaz Date: Mon, 5 Oct 2026 14:08:35 -0400 Subject: [PATCH 10/10] Match the current nightly formatter --- crates/leo/src/cli/commands/devnode/rest/mod.rs | 10 ++++------ crates/parser/src/rowan.rs | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/crates/leo/src/cli/commands/devnode/rest/mod.rs b/crates/leo/src/cli/commands/devnode/rest/mod.rs index a02773c18cc..7698b686b36 100644 --- a/crates/leo/src/cli/commands/devnode/rest/mod.rs +++ b/crates/leo/src/cli/commands/devnode/rest/mod.rs @@ -135,7 +135,6 @@ impl> Rest { let routes = axum::Router::new() // Get ../consensus_version .route("/consensus_version", get(Self::get_consensus_version)) - // GET ../block/.. .route("/block/height/latest", get(Self::get_block_height_latest)) .route("/block/hash/latest", get(Self::get_block_hash_latest)) @@ -146,21 +145,21 @@ impl> Rest { .route("/block/{height_or_hash}/header", get(Self::get_block_header)) .route("/block/{height_or_hash}/transactions", get(Self::get_block_transactions)) .route("/block/create", post(Self::create_block)) - // GET and POST ../transaction/.. .route("/transaction/broadcast", post(Self::transaction_broadcast)) .route("/transaction/confirmed/{id}", get(Self::get_confirmed_transaction)) .route("/transaction/unconfirmed/{id}", get(Self::get_unconfirmed_transaction)) .route("/transaction/{id}", get(Self::get_transaction)) - // GET ../find/.. .route("/find/blockHash/{tx_id}", get(Self::find_block_hash)) .route("/find/blockHeight/{state_root}", get(Self::find_block_height_from_state_root)) .route("/find/transactionID/deployment/{program_id}", get(Self::find_latest_transaction_id_from_program_id)) - .route("/find/transactionID/deployment/{program_id}/{edition}", get(Self::find_transaction_id_from_program_id_and_edition)) + .route( + "/find/transactionID/deployment/{program_id}/{edition}", + get(Self::find_transaction_id_from_program_id_and_edition), + ) .route("/find/transactionID/{transition_id}", get(Self::find_transaction_id_from_transition_id)) .route("/find/transitionID/{input_or_output_id}", get(Self::find_transition_id)) - // GET ../program/.. .route("/program/{id}", get(Self::get_program)) .route("/program/{id}/latest_edition", get(Self::get_latest_program_edition)) @@ -170,7 +169,6 @@ impl> Rest { .route("/program/{id}/mapping/{name}", get(Self::get_mapping_values)) .route("/program/{id}/view/{function}", post(Self::evaluate_view_latest)) .route("/program/{id}/view/{function}/{height}", post(Self::evaluate_view)) - // GET misc endpoints. .route("/blocks", get(Self::get_blocks)) .route("/height/{hash}", get(Self::get_height)) diff --git a/crates/parser/src/rowan.rs b/crates/parser/src/rowan.rs index e80b266a9fc..2343ad9b791 100644 --- a/crates/parser/src/rowan.rs +++ b/crates/parser/src/rowan.rs @@ -876,7 +876,7 @@ impl<'a> ConversionContext<'a> { // Collect arguments (remaining expression children) let arguments = children(node) - .skip(1) // Skip the callee + .skip(1) // Skip the callee .filter(|n| n.kind().is_expression()) .map(|n| self.to_expression(&n)) .collect::>>()?;