From ea4730472e4a39f95ee7875a3350c62265016acf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Senovilla=20Polo?= Date: Wed, 26 Nov 2025 13:26:18 +0100 Subject: [PATCH 01/18] feat: Add verifiable flag to pop build --- crates/pop-cli/src/commands/build/mod.rs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/crates/pop-cli/src/commands/build/mod.rs b/crates/pop-cli/src/commands/build/mod.rs index 690aa0fbc..ebe0394ae 100644 --- a/crates/pop-cli/src/commands/build/mod.rs +++ b/crates/pop-cli/src/commands/build/mod.rs @@ -84,10 +84,10 @@ pub(crate) struct BuildArgs { #[clap(long, help_heading = CONTRACT_HELP_HEADER)] #[cfg(feature = "contract")] pub(crate) metadata: Option, - /// Whether to build in a way that the contract is verifiable - ///#[clap(long, help_heading = CONTRACT_HELP_HEADER)] - ///#[cfg(feature = "contract")] - ///pub(crate) verifiable: bool + /// Whether to build in a way that the contract is verifiable + #[clap(long, help_heading = CONTRACT_HELP_HEADER)] + #[cfg(feature = "contract")] + pub(crate) verifiable: bool, } /// Subcommand for building chain artifacts. @@ -361,6 +361,8 @@ mod tests { only_runtime: false, #[cfg(feature = "contract")] metadata: None, + #[cfg(feature = "contract")] + verifiable: false }, project_path, &mut cli, @@ -429,6 +431,8 @@ mod tests { only_runtime: false, #[cfg(feature = "contract")] metadata: None, + #[cfg(feature = "contract")] + verifiable: false, })?; Ok(()) @@ -474,6 +478,8 @@ mod tests { only_runtime: false, #[cfg(feature = "contract")] metadata: None, + #[cfg(feature = "contract")] + verifiable: false, })?; // Test 2: Execute with production profile @@ -496,6 +502,8 @@ mod tests { only_runtime: false, #[cfg(feature = "contract")] metadata: None, + #[cfg(feature = "contract")] + verifiable: false, })?; // Test 3: Execute with custom features @@ -515,6 +523,8 @@ mod tests { only_runtime: false, #[cfg(feature = "contract")] metadata: None, + #[cfg(feature = "contract")] + verifiable: false, })?; } @@ -538,6 +548,8 @@ mod tests { only_runtime: false, #[cfg(feature = "contract")] metadata: None, + #[cfg(feature = "contract")] + verifiable: false, })?; // Test 5: Execute with path_pos instead of path @@ -560,6 +572,8 @@ mod tests { only_runtime: false, #[cfg(feature = "contract")] metadata: None, + #[cfg(feature = "contract")] + verifiable: false, })?; // Test 6: Execute with benchmark and try_runtime flags @@ -579,6 +593,8 @@ mod tests { only_runtime: false, #[cfg(feature = "contract")] metadata: None, + #[cfg(feature = "contract")] + verifiable: false, })?; } From faae645cc60e9775889fbb68d28fb133f6f74850 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Senovilla=20Polo?= Date: Wed, 26 Nov 2025 18:40:51 +0100 Subject: [PATCH 02/18] chore: Refactor on build_smart_contract function to allow verifiable builds from cargo contract --- crates/pop-cli/src/commands/build/contract.rs | 9 +++++---- crates/pop-cli/src/commands/build/mod.rs | 16 ++++++++++------ crates/pop-cli/src/commands/call/contract.rs | 11 ++++++++--- crates/pop-cli/src/commands/up/contract.rs | 9 +++++++-- crates/pop-contracts/src/build.rs | 18 +++++++++--------- crates/pop-contracts/src/lib.rs | 2 +- 6 files changed, 40 insertions(+), 25 deletions(-) diff --git a/crates/pop-cli/src/commands/build/contract.rs b/crates/pop-cli/src/commands/build/contract.rs index d11a8e679..f239c3de2 100644 --- a/crates/pop-cli/src/commands/build/contract.rs +++ b/crates/pop-cli/src/commands/build/contract.rs @@ -1,15 +1,16 @@ // SPDX-License-Identifier: GPL-3.0 use crate::cli; -use pop_contracts::{MetadataSpec, Verbosity, build_smart_contract}; +use pop_contracts::{BuildMode, MetadataSpec, Verbosity, build_smart_contract}; use std::path::PathBuf; /// Configuration for building a smart contract. pub struct BuildContract { /// Path of the contract project. pub(crate) path: PathBuf, - /// Build profile: `true` for release mode, `false` for debug mode. - pub(crate) release: bool, + /// Build profile: `true` for release mode, `false` for debug mode, `verifiable` for + /// deterministic, release mode. + pub(crate) build_mode: BuildMode, /// Which specification to use for contract metadata. pub(crate) metadata: Option, } @@ -28,7 +29,7 @@ impl BuildContract { cli.intro("Building your contract")?; // Build contract. let build_result = - build_smart_contract(&self.path, self.release, Verbosity::Default, self.metadata)?; + build_smart_contract(&self.path, self.build_mode, Verbosity::Default, self.metadata)?; cli.success(build_result.display())?; cli.outro("Build completed successfully!")?; Ok("contract") diff --git a/crates/pop-cli/src/commands/build/mod.rs b/crates/pop-cli/src/commands/build/mod.rs index ebe0394ae..f09214dcd 100644 --- a/crates/pop-cli/src/commands/build/mod.rs +++ b/crates/pop-cli/src/commands/build/mod.rs @@ -7,7 +7,7 @@ use contract::BuildContract; use duct::cmd; use pop_common::Profile; #[cfg(feature = "contract")] -use pop_contracts::MetadataSpec; +use pop_contracts::{BuildMode, MetadataSpec}; use serde::Serialize; use std::path::{Path, PathBuf}; #[cfg(feature = "chain")] @@ -85,7 +85,7 @@ pub(crate) struct BuildArgs { #[cfg(feature = "contract")] pub(crate) metadata: Option, /// Whether to build in a way that the contract is verifiable - #[clap(long, help_heading = CONTRACT_HELP_HEADER)] + #[clap(long, conflicts_with_all = ["profile", "release"], help_heading = CONTRACT_HELP_HEADER)] #[cfg(feature = "contract")] pub(crate) verifiable: bool, } @@ -121,11 +121,15 @@ impl Command { #[cfg(feature = "contract")] if pop_contracts::is_supported(&project_path)? { // All commands originating from root command are valid - let release = match &args.profile { - Some(profile) => (*profile).into(), - None => args.release, + let build_mode = match (&args.profile, &args.verifiable) { + (Some(Profile::Release), false) | (Some(Profile::Production), false) => + BuildMode::Release, + (None, true) => BuildMode::Verifiable, + (None, false) if args.release => BuildMode::Release, + // Fallback to debug mode + _ => BuildMode::Debug, }; - BuildContract { path: project_path, release, metadata: args.metadata }.execute()?; + BuildContract { path: project_path, build_mode, metadata: args.metadata }.execute()?; return Ok(()); } diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index 10bb27ab3..eab7ba424 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -19,8 +19,8 @@ use clap::Args; use cliclack::spinner; use pop_common::{DefaultConfig, Keypair, parse_h160_account}; use pop_contracts::{ - CallExec, CallOpts, ContractCallable, ContractFunction, ContractStorage, DefaultEnvironment, - Verbosity, Weight, build_smart_contract, call_smart_contract, + BuildMode, CallExec, CallOpts, ContractCallable, ContractFunction, ContractStorage, + DefaultEnvironment, Verbosity, Weight, build_smart_contract, call_smart_contract, call_smart_contract_from_signed_payload, dry_run_gas_estimate_call, fetch_contract_storage, get_call_payload, get_contract_storage_info, get_messages, set_up_call, }; @@ -202,7 +202,12 @@ impl CallContractCommand { cli.warning("NOTE: contract has not yet been built.")?; let spinner = spinner(); spinner.start("Building contract in RELEASE mode..."); - let result = match build_smart_contract(&project_path, true, Verbosity::Quiet, None) { + let result = match build_smart_contract( + &project_path, + BuildMode::Release, + Verbosity::Quiet, + None, + ) { Ok(result) => result, Err(e) => { return Err(anyhow!(format!( diff --git a/crates/pop-cli/src/commands/up/contract.rs b/crates/pop-cli/src/commands/up/contract.rs index cb2a5ec96..52da4809a 100644 --- a/crates/pop-cli/src/commands/up/contract.rs +++ b/crates/pop-cli/src/commands/up/contract.rs @@ -21,7 +21,7 @@ use clap::Args; use cliclack::spinner; use console::Emoji; use pop_contracts::{ - FunctionType, UpOpts, Verbosity, Weight, build_smart_contract, + BuildMode, FunctionType, UpOpts, Verbosity, Weight, build_smart_contract, dry_run_gas_estimate_instantiate, dry_run_upload, extract_function, get_contract_code, get_instantiate_payload, get_upload_payload, instantiate_contract_signed, instantiate_smart_contract, is_chain_alive, run_eth_rpc_node, run_ink_node, set_up_deployment, @@ -160,7 +160,12 @@ impl UpContractCommand { } let spinner = spinner(); spinner.start("Building contract in RELEASE mode..."); - let result = match build_smart_contract(&self.path, true, Verbosity::Quiet, None) { + let result = match build_smart_contract( + &self.path, + BuildMode::Release, + Verbosity::Quiet, + None, + ) { Ok(result) => result, Err(e) => { Cli.outro_cancel(format!("🚫 An error occurred building your contract: {e}\nUse `pop build` to retry with build output."))?; diff --git a/crates/pop-contracts/src/build.rs b/crates/pop-contracts/src/build.rs index f59a62808..ea105ca37 100644 --- a/crates/pop-contracts/src/build.rs +++ b/crates/pop-contracts/src/build.rs @@ -1,8 +1,8 @@ // SPDX-License-Identifier: GPL-3.0 use crate::{errors::Error, utils::get_manifest_path}; -use contract_build::{BuildMode, BuildResult, ExecuteArgs, execute}; -pub use contract_build::{MetadataSpec, Verbosity}; +pub use contract_build::{BuildMode, MetadataSpec, Verbosity}; +use contract_build::{BuildResult, ExecuteArgs, execute}; use std::path::Path; /// Build the smart contract located at the specified `path` in `build_release` mode. @@ -15,21 +15,21 @@ use std::path::Path; /// * `metadata_spec` - Optionally specify the contract metadata format/version. pub fn build_smart_contract( path: &Path, - release: bool, + build_mode: BuildMode, verbosity: Verbosity, metadata_spec: Option, ) -> anyhow::Result { let manifest_path = get_manifest_path(path)?; - let build_mode = match release { - true => BuildMode::Release, - false => BuildMode::Debug, - }; - let args = ExecuteArgs { manifest_path, build_mode, verbosity, metadata_spec, ..Default::default() }; + // Execute the build and log the output of the build - execute(args) + match build_mode { + // For verifiable contracts, execute calls docker_build (https://github.com/use-ink/cargo-contract/blob/master/crates/build/src/lib.rs#L595) which launchs a blocking tokio runtime to handle the async operations (https://github.com/use-ink/cargo-contract/blob/master/crates/build/src/docker.rs#L135). The issue is that pop is itself a tokio runtime, launching another blocking one isn't allowed by tokio. So for verifiable contracts we need to first block the main pop tokio runtime before calling execute + BuildMode::Verifiable => tokio::task::block_in_place(|| execute(args)), + _ => execute(args), + } } /// Determines whether the manifest at the supplied path is a supported smart contract project. diff --git a/crates/pop-contracts/src/lib.rs b/crates/pop-contracts/src/lib.rs index 9b145276a..054fd86bb 100644 --- a/crates/pop-contracts/src/lib.rs +++ b/crates/pop-contracts/src/lib.rs @@ -13,7 +13,7 @@ mod testing; mod up; mod utils; -pub use build::{Verbosity, build_smart_contract, is_supported}; +pub use build::{BuildMode, Verbosity, build_smart_contract, is_supported}; pub use call::{ CallOpts, call_smart_contract, call_smart_contract_from_signed_payload, dry_run_call, dry_run_gas_estimate_call, get_call_payload, set_up_call, From 233422ee849b1fa3ee070ae95ba263ad654f6852 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Senovilla=20Polo?= Date: Thu, 27 Nov 2025 08:27:03 +0100 Subject: [PATCH 03/18] chore: Point to cargo-contract needed commit til it's merged --- Cargo.lock | 12 ++++-------- Cargo.toml | 6 +++--- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9961bc297..e68d97a0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2098,8 +2098,7 @@ checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "contract-build" version = "6.0.0-beta.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66ae0d8673738134e7e63d672685287486dc2054a570081f9291b8b26fbd989d" +source = "git+https://github.com/use-ink/cargo-contract?rev=c7aba2e#c7aba2e05474bd5dd70d7ab81195cab2994710d6" dependencies = [ "alloy-json-abi", "anyhow", @@ -2142,8 +2141,7 @@ dependencies = [ [[package]] name = "contract-extrinsics" version = "6.0.0-beta.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19e4b6d9f2effa08a76ddfe317ac1370298bb25e2b938aa15122eabb9dd26c5" +source = "git+https://github.com/use-ink/cargo-contract?rev=c7aba2e#c7aba2e05474bd5dd70d7ab81195cab2994710d6" dependencies = [ "anyhow", "colored", @@ -2173,8 +2171,7 @@ dependencies = [ [[package]] name = "contract-metadata" version = "6.0.0-beta.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8401a04d171e2867eaf270755b4dbd852a260541dbeb0f5085b4b63ec63be88" +source = "git+https://github.com/use-ink/cargo-contract?rev=c7aba2e#c7aba2e05474bd5dd70d7ab81195cab2994710d6" dependencies = [ "anyhow", "impl-serde", @@ -2187,8 +2184,7 @@ dependencies = [ [[package]] name = "contract-transcode" version = "6.0.0-beta.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4ddd5e0f972d2358bea1db6469de2d5c5a70143b1a98d433c207e86981a0417" +source = "git+https://github.com/use-ink/cargo-contract?rev=c7aba2e#c7aba2e05474bd5dd70d7ab81195cab2994710d6" dependencies = [ "anyhow", "base58", diff --git a/Cargo.toml b/Cargo.toml index ef4872d61..bc00a5b86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,9 +57,9 @@ sp-weights = { version = "33.0.0", default-features = false } scale = { package = "parity-scale-codec", version = "3.7.5", features = ["derive"] } scale-info = { version = "2.11.6", default-features = false, features = ["derive"] } scale-value = { version = "0.18.0", default-features = false, features = ["from-string", "parser-ss58"] } -contract-build = { version = "6.0.0-beta.1", default-features = false } -contract-extrinsics = { version = "6.0.0-beta.1", default-features = false } -contract-transcode = { version = "6.0.0-beta.1", default-features = false } +contract-build = { git = "https://github.com/use-ink/cargo-contract", rev = "c7aba2e", default-features = false } +contract-extrinsics = { git = "https://github.com/use-ink/cargo-contract", rev = "c7aba2e", default-features = false } +contract-transcode = { git = "https://github.com/use-ink/cargo-contract", rev = "c7aba2e", default-features = false } heck = { version = "0.5.0", default-features = false } # parachains From cd8601e79b2b3665bc93fef66f4068a97b78d00b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Senovilla=20Polo?= Date: Thu, 27 Nov 2025 08:27:25 +0100 Subject: [PATCH 04/18] chore: resolve_build_mode function + test --- crates/pop-cli/src/commands/build/contract.rs | 53 +++++++++++++++++++ crates/pop-cli/src/commands/build/mod.rs | 11 +--- 2 files changed, 55 insertions(+), 9 deletions(-) diff --git a/crates/pop-cli/src/commands/build/contract.rs b/crates/pop-cli/src/commands/build/contract.rs index f239c3de2..64cc1a1e1 100644 --- a/crates/pop-cli/src/commands/build/contract.rs +++ b/crates/pop-cli/src/commands/build/contract.rs @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 +use super::{BuildArgs, Profile}; use crate::cli; use pop_contracts::{BuildMode, MetadataSpec, Verbosity, build_smart_contract}; use std::path::PathBuf; @@ -35,3 +36,55 @@ impl BuildContract { Ok("contract") } } + +pub(super) fn resolve_build_mode(args: &BuildArgs) -> BuildMode { + match (&args.profile, &args.verifiable) { + (Some(Profile::Release), false) | (Some(Profile::Production), false) => BuildMode::Release, + (None, true) => BuildMode::Verifiable, + (None, false) if args.release => BuildMode::Release, + // Fallback to debug mode + _ => BuildMode::Debug, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn resolve_build_mode_works() { + // Profile::Release + verifiable=false -> Release + assert_eq!( + resolve_build_mode(&BuildArgs { + profile: Some(Profile::Release), + ..Default::default() + }), + BuildMode::Release + ); + // Profile::Production + verifiable=false -> Release + assert_eq!( + resolve_build_mode(&BuildArgs { + profile: Some(Profile::Production), + ..Default::default() + }), + BuildMode::Release + ); + // No profile + verifiable=true -> Verifiable + assert_eq!( + resolve_build_mode(&BuildArgs { verifiable: true, ..Default::default() }), + BuildMode::Verifiable + ); + // No profile + verifiable=false + release=true -> Release + assert_eq!( + resolve_build_mode(&BuildArgs { release: true, ..Default::default() }), + BuildMode::Release + ); + // Profile::Debug + verifiable=false -> Debug + assert_eq!( + resolve_build_mode(&BuildArgs { profile: Some(Profile::Debug), ..Default::default() }), + BuildMode::Debug + ); + // No profile + verifiable=false + release=false -> Debug + assert_eq!(resolve_build_mode(&BuildArgs::default()), BuildMode::Debug); + } +} diff --git a/crates/pop-cli/src/commands/build/mod.rs b/crates/pop-cli/src/commands/build/mod.rs index f09214dcd..cac5d3ff4 100644 --- a/crates/pop-cli/src/commands/build/mod.rs +++ b/crates/pop-cli/src/commands/build/mod.rs @@ -7,7 +7,7 @@ use contract::BuildContract; use duct::cmd; use pop_common::Profile; #[cfg(feature = "contract")] -use pop_contracts::{BuildMode, MetadataSpec}; +use pop_contracts::MetadataSpec; use serde::Serialize; use std::path::{Path, PathBuf}; #[cfg(feature = "chain")] @@ -121,14 +121,7 @@ impl Command { #[cfg(feature = "contract")] if pop_contracts::is_supported(&project_path)? { // All commands originating from root command are valid - let build_mode = match (&args.profile, &args.verifiable) { - (Some(Profile::Release), false) | (Some(Profile::Production), false) => - BuildMode::Release, - (None, true) => BuildMode::Verifiable, - (None, false) if args.release => BuildMode::Release, - // Fallback to debug mode - _ => BuildMode::Debug, - }; + let build_mode = contract::resolve_build_mode(&args); BuildContract { path: project_path, build_mode, metadata: args.metadata }.execute()?; return Ok(()); } From 74fde118a8169019a4ab0f1e8b29883e119951fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Senovilla=20Polo?= Date: Thu, 27 Nov 2025 09:45:31 +0100 Subject: [PATCH 05/18] test: Verifiable contract integration test --- .github/workflows/integration-tests.yml | 8 ++++ crates/pop-cli/src/commands/build/contract.rs | 4 ++ crates/pop-cli/tests/contract.rs | 37 +++++++++++++++++++ 3 files changed, 49 insertions(+) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 0f9ad0545..3fc4bb889 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -77,6 +77,14 @@ jobs: brew install protobuf protoc --version + - name: Setup Docker (macOS) + if: matrix.os == 'macos-latest' + uses: douglascamata/setup-docker-macos-action@v1-alpha + + - name: Ensure Docker is running + run: | + docker info || (sudo systemctl start docker && docker info) + - name: Install cargo-nextest uses: taiki-e/install-action@v2 with: diff --git a/crates/pop-cli/src/commands/build/contract.rs b/crates/pop-cli/src/commands/build/contract.rs index 64cc1a1e1..a41952c37 100644 --- a/crates/pop-cli/src/commands/build/contract.rs +++ b/crates/pop-cli/src/commands/build/contract.rs @@ -37,6 +37,10 @@ impl BuildContract { } } +/// Resolve the `BuildMode` to use in a contract build depending on the specified args +/// +/// # Arguments +/// * `args` - The `BuildArgs` needed to resolve the `BuildMode` pub(super) fn resolve_build_mode(args: &BuildArgs) -> BuildMode { match (&args.profile, &args.verifiable) { (Some(Profile::Release), false) | (Some(Profile::Production), false) => BuildMode::Release, diff --git a/crates/pop-cli/tests/contract.rs b/crates/pop-cli/tests/contract.rs index a749f7416..c760ee963 100644 --- a/crates/pop-cli/tests/contract.rs +++ b/crates/pop-cli/tests/contract.rs @@ -12,6 +12,7 @@ use pop_contracts::{ set_up_call, set_up_deployment, }; use serde::{Deserialize, Serialize}; +use serde_json::Value; use std::{path::Path, time::Duration}; use strum::VariantArray; use subxt::{ @@ -272,6 +273,41 @@ async fn contract_lifecycle() -> Result<()> { Ok(()) } +#[tokio::test] +async fn verifiable_contract_lifecycle() -> Result<()>{ + // TODO: Incomplete test, we'll be adding more steps as the development of the feature advance + let temp = tempfile::tempdir()?; + let temp_dir = temp.path(); + //let temp_dir = Path::new("./"); //For testing locally + // pop new contract test_contract (default) + let mut command = pop( + temp_dir, + ["new", "contract", "test_contract", "--template", "standard"], + ); + assert!(command.spawn()?.wait().await?.success()); + let contract_dir = temp_dir.join("test_contract"); + assert!(contract_dir.exists()); + + // pop build --path ./test_contract --verifiable + command = pop(&contract_dir, ["build", "--verifiable"]); + assert!(command.spawn()?.wait().await?.success()); + + let ink_target_path = contract_dir.join("target").join("ink"); + assert!(ink_target_path.join("test_contract.contract").exists()); + assert!(ink_target_path.join("test_contract.polkavm").exists()); + let metadata_path = ink_target_path.join("test_contract.json"); + assert!(metadata_path.exists()); + let metadata_contents: Value = serde_json::from_str(&std::fs::read_to_string(&metadata_path)?)?; + // Verifiable builds include the used image (useink/contracts-verifiable:{tag}) in the metadata so that they can be exactly reproduced + let image_key = metadata_contents.get("image"); + match image_key{ + Some(Value::String(value)) if value.starts_with("useink/contracts-verifiable") => (), + _ => return Err(anyhow::anyhow!("Verifiable build doesn't include the expected image")) + } + + Ok(()) +} + async fn generate_all_the_templates(temp_dir: &Path) -> Result<()> { for template in Contract::VARIANTS { let contract_name = format!("test_contract_{}", template).replace("-", "_"); @@ -282,3 +318,4 @@ async fn generate_all_the_templates(temp_dir: &Path) -> Result<()> { } Ok(()) } + From 9f2ad371c976af515843149b0b88418f826c0218 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Senovilla=20Polo?= Date: Thu, 27 Nov 2025 10:50:07 +0100 Subject: [PATCH 06/18] chore: Alias path for verifiable contract builds + allow custom image support --- crates/pop-cli/src/commands/build/contract.rs | 51 +++++++++++++++++-- crates/pop-cli/src/commands/build/mod.rs | 34 ++++++++++--- crates/pop-cli/src/commands/call/contract.rs | 1 + crates/pop-cli/src/commands/up/contract.rs | 1 + crates/pop-cli/tests/contract.rs | 41 +++++++-------- crates/pop-contracts/src/build.rs | 9 +++- crates/pop-contracts/src/lib.rs | 2 +- crates/pop-contracts/src/utils/mod.rs | 33 +++++++++--- 8 files changed, 131 insertions(+), 41 deletions(-) diff --git a/crates/pop-cli/src/commands/build/contract.rs b/crates/pop-cli/src/commands/build/contract.rs index a41952c37..e71c83e42 100644 --- a/crates/pop-cli/src/commands/build/contract.rs +++ b/crates/pop-cli/src/commands/build/contract.rs @@ -2,7 +2,7 @@ use super::{BuildArgs, Profile}; use crate::cli; -use pop_contracts::{BuildMode, MetadataSpec, Verbosity, build_smart_contract}; +use pop_contracts::{BuildMode, ImageVariant, MetadataSpec, Verbosity, build_smart_contract}; use std::path::PathBuf; /// Configuration for building a smart contract. @@ -14,6 +14,8 @@ pub struct BuildContract { pub(crate) build_mode: BuildMode, /// Which specification to use for contract metadata. pub(crate) metadata: Option, + /// A custom image for a verifiable build + pub(crate) image: Option, } impl BuildContract { @@ -29,8 +31,13 @@ impl BuildContract { fn build(self, cli: &mut impl cli::traits::Cli) -> anyhow::Result<&'static str> { cli.intro("Building your contract")?; // Build contract. - let build_result = - build_smart_contract(&self.path, self.build_mode, Verbosity::Default, self.metadata)?; + let build_result = build_smart_contract( + &self.path, + self.build_mode, + Verbosity::Default, + self.metadata, + self.image, + )?; cli.success(build_result.display())?; cli.outro("Build completed successfully!")?; Ok("contract") @@ -42,7 +49,7 @@ impl BuildContract { /// # Arguments /// * `args` - The `BuildArgs` needed to resolve the `BuildMode` pub(super) fn resolve_build_mode(args: &BuildArgs) -> BuildMode { - match (&args.profile, &args.verifiable) { + match (&args.profile, args.verifiable) { (Some(Profile::Release), false) | (Some(Profile::Production), false) => BuildMode::Release, (None, true) => BuildMode::Verifiable, (None, false) if args.release => BuildMode::Release, @@ -51,6 +58,16 @@ pub(super) fn resolve_build_mode(args: &BuildArgs) -> BuildMode { } } +pub(super) fn resolve_image(args: &BuildArgs) -> anyhow::Result> { + match (&args.image, args.verifiable) { + (Some(image), true) => Ok(Some(ImageVariant::Custom(image.clone()))), + (None, true) => Ok(Some(ImageVariant::Default)), + (None, false) => Ok(None), + (Some(_), false) => + Err(anyhow::anyhow!("Custom images can only be used in verifiable builds")), + } +} + #[cfg(test)] mod tests { use super::*; @@ -91,4 +108,30 @@ mod tests { // No profile + verifiable=false + release=false -> Debug assert_eq!(resolve_build_mode(&BuildArgs::default()), BuildMode::Debug); } + + #[test] + fn resolve_image_works() { + // Custom image + verifiable=true -> Custom image + assert!(matches!(resolve_image(&BuildArgs { + image: Some("my-image:latest".to_string()), + verifiable: true, + ..Default::default() + }), Ok(Some(ImageVariant::Custom(custom))) if custom == "my-image:latest" + )); + // No image + verifiable=true -> Default image + assert!(matches!( + resolve_image(&BuildArgs { verifiable: true, ..Default::default() }), + Ok(Some(ImageVariant::Default)) + )); + // No image + verifiable=false -> None + assert!(matches!(resolve_image(&BuildArgs::default()), Ok(None))); + // Custom image + verifiable=false -> Error + let err = resolve_image(&BuildArgs { + image: Some("my-image:latest".to_string()), + verifiable: false, + ..Default::default() + }) + .unwrap_err(); + assert_eq!(err.to_string(), "Custom images can only be used in verifiable builds"); + } } diff --git a/crates/pop-cli/src/commands/build/mod.rs b/crates/pop-cli/src/commands/build/mod.rs index cac5d3ff4..f440b2aca 100644 --- a/crates/pop-cli/src/commands/build/mod.rs +++ b/crates/pop-cli/src/commands/build/mod.rs @@ -47,7 +47,7 @@ pub(crate) struct BuildArgs { #[cfg(feature = "chain")] pub command: Option, /// Directory path with flag for your project [default: current directory] - #[arg(long)] + #[arg(long, visible_alias = "manifest-path")] pub(crate) path: Option, /// Directory path without flag for your project [default: current directory] #[arg(value_name = "PATH", index = 1, conflicts_with = "path")] @@ -84,10 +84,15 @@ pub(crate) struct BuildArgs { #[clap(long, help_heading = CONTRACT_HELP_HEADER)] #[cfg(feature = "contract")] pub(crate) metadata: Option, - /// Whether to build in a way that the contract is verifiable - #[clap(long, conflicts_with_all = ["profile", "release"], help_heading = CONTRACT_HELP_HEADER)] + /// Whether to build in a way that the contract is verifiable. For verifiable contracts, use + /// --manifest-path instead of --path to directly point to your contracts Cargo.toml + #[clap(long, conflicts_with_all = ["release", "profile"], help_heading = CONTRACT_HELP_HEADER)] #[cfg(feature = "contract")] pub(crate) verifiable: bool, + /// Custom image for verifiable builds + #[clap(long, help_heading = CONTRACT_HELP_HEADER)] + #[cfg(feature = "contract")] + pub(crate) image: Option, } /// Subcommand for building chain artifacts. @@ -120,9 +125,10 @@ impl Command { #[cfg(feature = "contract")] if pop_contracts::is_supported(&project_path)? { - // All commands originating from root command are valid let build_mode = contract::resolve_build_mode(&args); - BuildContract { path: project_path, build_mode, metadata: args.metadata }.execute()?; + let image = contract::resolve_image(&args)?; + BuildContract { path: project_path, build_mode, metadata: args.metadata, image } + .execute()?; return Ok(()); } @@ -359,7 +365,9 @@ mod tests { #[cfg(feature = "contract")] metadata: None, #[cfg(feature = "contract")] - verifiable: false + verifiable: false, + #[cfg(feature = "contract")] + image: None, }, project_path, &mut cli, @@ -430,6 +438,8 @@ mod tests { metadata: None, #[cfg(feature = "contract")] verifiable: false, + #[cfg(feature = "contract")] + image: None, })?; Ok(()) @@ -477,6 +487,8 @@ mod tests { metadata: None, #[cfg(feature = "contract")] verifiable: false, + #[cfg(feature = "contract")] + image: None, })?; // Test 2: Execute with production profile @@ -501,6 +513,8 @@ mod tests { metadata: None, #[cfg(feature = "contract")] verifiable: false, + #[cfg(feature = "contract")] + image: None, })?; // Test 3: Execute with custom features @@ -522,6 +536,8 @@ mod tests { metadata: None, #[cfg(feature = "contract")] verifiable: false, + #[cfg(feature = "contract")] + image: None, })?; } @@ -547,6 +563,8 @@ mod tests { metadata: None, #[cfg(feature = "contract")] verifiable: false, + #[cfg(feature = "contract")] + image: None, })?; // Test 5: Execute with path_pos instead of path @@ -571,6 +589,8 @@ mod tests { metadata: None, #[cfg(feature = "contract")] verifiable: false, + #[cfg(feature = "contract")] + image: None, })?; // Test 6: Execute with benchmark and try_runtime flags @@ -592,6 +612,8 @@ mod tests { metadata: None, #[cfg(feature = "contract")] verifiable: false, + #[cfg(feature = "contract")] + image: None, })?; } diff --git a/crates/pop-cli/src/commands/call/contract.rs b/crates/pop-cli/src/commands/call/contract.rs index eab7ba424..1e6b26f6c 100644 --- a/crates/pop-cli/src/commands/call/contract.rs +++ b/crates/pop-cli/src/commands/call/contract.rs @@ -207,6 +207,7 @@ impl CallContractCommand { BuildMode::Release, Verbosity::Quiet, None, + None, ) { Ok(result) => result, Err(e) => { diff --git a/crates/pop-cli/src/commands/up/contract.rs b/crates/pop-cli/src/commands/up/contract.rs index 52da4809a..dafd2ff41 100644 --- a/crates/pop-cli/src/commands/up/contract.rs +++ b/crates/pop-cli/src/commands/up/contract.rs @@ -165,6 +165,7 @@ impl UpContractCommand { BuildMode::Release, Verbosity::Quiet, None, + None, ) { Ok(result) => result, Err(e) => { diff --git a/crates/pop-cli/tests/contract.rs b/crates/pop-cli/tests/contract.rs index c760ee963..e171450c6 100644 --- a/crates/pop-cli/tests/contract.rs +++ b/crates/pop-cli/tests/contract.rs @@ -274,38 +274,36 @@ async fn contract_lifecycle() -> Result<()> { } #[tokio::test] -async fn verifiable_contract_lifecycle() -> Result<()>{ - // TODO: Incomplete test, we'll be adding more steps as the development of the feature advance +async fn verifiable_contract_lifecycle() -> Result<()> { + // TODO: Incomplete test, we'll be adding more steps as the development of the feature advance let temp = tempfile::tempdir()?; let temp_dir = temp.path(); //let temp_dir = Path::new("./"); //For testing locally // pop new contract test_contract (default) - let mut command = pop( - temp_dir, - ["new", "contract", "test_contract", "--template", "standard"], - ); + let mut command = pop(temp_dir, ["new", "contract", "test_contract", "--template", "standard"]); assert!(command.spawn()?.wait().await?.success()); - let contract_dir = temp_dir.join("test_contract"); + let contract_dir = temp_dir.join("test_contract"); assert!(contract_dir.exists()); - // pop build --path ./test_contract --verifiable + // pop build --verifiable command = pop(&contract_dir, ["build", "--verifiable"]); assert!(command.spawn()?.wait().await?.success()); - let ink_target_path = contract_dir.join("target").join("ink"); - assert!(ink_target_path.join("test_contract.contract").exists()); - assert!(ink_target_path.join("test_contract.polkavm").exists()); - let metadata_path = ink_target_path.join("test_contract.json"); - assert!(metadata_path.exists()); - let metadata_contents: Value = serde_json::from_str(&std::fs::read_to_string(&metadata_path)?)?; - // Verifiable builds include the used image (useink/contracts-verifiable:{tag}) in the metadata so that they can be exactly reproduced - let image_key = metadata_contents.get("image"); - match image_key{ - Some(Value::String(value)) if value.starts_with("useink/contracts-verifiable") => (), - _ => return Err(anyhow::anyhow!("Verifiable build doesn't include the expected image")) - } + let ink_target_path = contract_dir.join("target").join("ink"); + assert!(ink_target_path.join("test_contract.contract").exists()); + assert!(ink_target_path.join("test_contract.polkavm").exists()); + let metadata_path = ink_target_path.join("test_contract.json"); + assert!(metadata_path.exists()); + let metadata_contents: Value = serde_json::from_str(&std::fs::read_to_string(&metadata_path)?)?; + // Verifiable builds include the used image (useink/contracts-verifiable:{tag} if not custom + // specified) in the metadata so that they can be exactly reproduced + let image_key = metadata_contents.get("image"); + match image_key { + Some(Value::String(value)) if value.starts_with("useink/contracts-verifiable") => (), + _ => return Err(anyhow::anyhow!("Verifiable build doesn't include the expected image")), + } - Ok(()) + Ok(()) } async fn generate_all_the_templates(temp_dir: &Path) -> Result<()> { @@ -318,4 +316,3 @@ async fn generate_all_the_templates(temp_dir: &Path) -> Result<()> { } Ok(()) } - diff --git a/crates/pop-contracts/src/build.rs b/crates/pop-contracts/src/build.rs index ea105ca37..47798ea8c 100644 --- a/crates/pop-contracts/src/build.rs +++ b/crates/pop-contracts/src/build.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 use crate::{errors::Error, utils::get_manifest_path}; -pub use contract_build::{BuildMode, MetadataSpec, Verbosity}; +pub use contract_build::{BuildMode, ImageVariant, MetadataSpec, Verbosity}; use contract_build::{BuildResult, ExecuteArgs, execute}; use std::path::Path; @@ -18,12 +18,17 @@ pub fn build_smart_contract( build_mode: BuildMode, verbosity: Verbosity, metadata_spec: Option, + image: Option, ) -> anyhow::Result { let manifest_path = get_manifest_path(path)?; - let args = + let mut args = ExecuteArgs { manifest_path, build_mode, verbosity, metadata_spec, ..Default::default() }; + if let Some(image) = image { + args.image = image; + } + // Execute the build and log the output of the build match build_mode { // For verifiable contracts, execute calls docker_build (https://github.com/use-ink/cargo-contract/blob/master/crates/build/src/lib.rs#L595) which launchs a blocking tokio runtime to handle the async operations (https://github.com/use-ink/cargo-contract/blob/master/crates/build/src/docker.rs#L135). The issue is that pop is itself a tokio runtime, launching another blocking one isn't allowed by tokio. So for verifiable contracts we need to first block the main pop tokio runtime before calling execute diff --git a/crates/pop-contracts/src/lib.rs b/crates/pop-contracts/src/lib.rs index 054fd86bb..54f1ee226 100644 --- a/crates/pop-contracts/src/lib.rs +++ b/crates/pop-contracts/src/lib.rs @@ -13,7 +13,7 @@ mod testing; mod up; mod utils; -pub use build::{BuildMode, Verbosity, build_smart_contract, is_supported}; +pub use build::{BuildMode, ImageVariant, Verbosity, build_smart_contract, is_supported}; pub use call::{ CallOpts, call_smart_contract, call_smart_contract_from_signed_payload, dry_run_call, dry_run_gas_estimate_call, get_call_payload, set_up_call, diff --git a/crates/pop-contracts/src/utils/mod.rs b/crates/pop-contracts/src/utils/mod.rs index cd4ba6c81..79021255b 100644 --- a/crates/pop-contracts/src/utils/mod.rs +++ b/crates/pop-contracts/src/utils/mod.rs @@ -15,11 +15,14 @@ pub mod metadata; /// Retrieves the manifest path for a contract project. /// /// # Arguments -/// * `path` - A path to the project directory. +/// * `path` - A path to the project directory or directly to a Cargo.toml file. pub fn get_manifest_path(path: &Path) -> Result { - let full_path = PathBuf::from(path.to_string_lossy().to_string()) - .join("Cargo.toml") - .canonicalize()?; + let full_path = if path.ends_with("Cargo.toml") { + path.to_path_buf() + } else { + PathBuf::from(path.to_string_lossy().to_string()).join("Cargo.toml") + } + .canonicalize()?; ManifestPath::try_from(Some(full_path)) .map_err(|e| Error::ManifestPath(format!("Failed to get manifest path: {e}"))) } @@ -74,9 +77,27 @@ mod tests { } #[test] - fn test_get_manifest_path() -> Result<(), Error> { + fn get_manifest_path_from_directory() -> Result<(), Error> { + let temp_dir = setup_test_environment()?; + let contract_path = temp_dir.path().join("test_contract"); + let manifest_path = get_manifest_path(&contract_path)?; + assert_eq!( + manifest_path.as_ref().canonicalize().unwrap(), + contract_path.join("Cargo.toml").canonicalize().unwrap() + ); + Ok(()) + } + + #[test] + fn get_manifest_path_from_cargo_toml() -> Result<(), Error> { let temp_dir = setup_test_environment()?; - get_manifest_path(&temp_dir.path().join("test_contract"))?; + let contract_path = temp_dir.path().join("test_contract"); + let manifest_path = + get_manifest_path(&temp_dir.path().join("test_contract").join("Cargo.toml"))?; + assert_eq!( + manifest_path.as_ref().canonicalize().unwrap(), + contract_path.join("Cargo.toml").canonicalize().unwrap() + ); Ok(()) } From 6fdeafa4054c4161bf06845c349c41d59843c94c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Senovilla=20Polo?= Date: Thu, 27 Nov 2025 10:57:14 +0100 Subject: [PATCH 07/18] ci: Update docker setup action for macOs --- .github/workflows/integration-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 3fc4bb889..e2ad68067 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -79,7 +79,7 @@ jobs: - name: Setup Docker (macOS) if: matrix.os == 'macos-latest' - uses: douglascamata/setup-docker-macos-action@v1-alpha + uses: docker/setup-docker-action@v4 - name: Ensure Docker is running run: | From 062fd1356269eea92b473c06fa3acad12a87bd4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Senovilla=20Polo?= Date: Thu, 27 Nov 2025 11:07:14 +0100 Subject: [PATCH 08/18] ci: Use intel mac architecture for docker --- .github/workflows/integration-tests.yml | 8 ++++---- crates/pop-cli/tests/contract.rs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index e2ad68067..9e0991e0b 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -35,7 +35,7 @@ jobs: matrix: os: - ubuntu-latest - - macos-latest + - macos-13 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -57,7 +57,7 @@ jobs: shared-key: shared-linux-contract${{ github.head_ref || github.ref_name }} - name: Rust Cache MacOS - if: matrix.os == 'macos-latest' + if: matrix.os == 'macos-13' uses: Swatinem/rust-cache@v2 with: cache-on-failure: true @@ -72,13 +72,13 @@ jobs: protoc --version - name: Install packages (macOS) - if: matrix.os == 'macos-latest' + if: matrix.os == 'macos-13' run: | brew install protobuf protoc --version - name: Setup Docker (macOS) - if: matrix.os == 'macos-latest' + if: matrix.os == 'macos-13' uses: docker/setup-docker-action@v4 - name: Ensure Docker is running diff --git a/crates/pop-cli/tests/contract.rs b/crates/pop-cli/tests/contract.rs index e171450c6..02d622e14 100644 --- a/crates/pop-cli/tests/contract.rs +++ b/crates/pop-cli/tests/contract.rs @@ -286,7 +286,7 @@ async fn verifiable_contract_lifecycle() -> Result<()> { assert!(contract_dir.exists()); // pop build --verifiable - command = pop(&contract_dir, ["build", "--verifiable"]); + command = pop(&temp_dir, ["build", "--manifest-path", "./test_contract/Cargo.toml", "--verifiable"]); assert!(command.spawn()?.wait().await?.success()); let ink_target_path = contract_dir.join("target").join("ink"); From 044881ffb691554c40de2ef9816c46ff89a52a23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Senovilla=20Polo?= Date: Thu, 27 Nov 2025 11:09:54 +0100 Subject: [PATCH 09/18] fmt --- crates/pop-cli/tests/contract.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/pop-cli/tests/contract.rs b/crates/pop-cli/tests/contract.rs index 02d622e14..22deefcff 100644 --- a/crates/pop-cli/tests/contract.rs +++ b/crates/pop-cli/tests/contract.rs @@ -286,7 +286,8 @@ async fn verifiable_contract_lifecycle() -> Result<()> { assert!(contract_dir.exists()); // pop build --verifiable - command = pop(&temp_dir, ["build", "--manifest-path", "./test_contract/Cargo.toml", "--verifiable"]); + command = + pop(&temp_dir, ["build", "--manifest-path", "./test_contract/Cargo.toml", "--verifiable"]); assert!(command.spawn()?.wait().await?.success()); let ink_target_path = contract_dir.join("target").join("ink"); From 3b9250bbd1fcd393059359504efc8515e9f49230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Senovilla=20Polo?= Date: Thu, 27 Nov 2025 11:36:28 +0100 Subject: [PATCH 10/18] clippy --- crates/pop-cli/src/commands/build/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/pop-cli/src/commands/build/mod.rs b/crates/pop-cli/src/commands/build/mod.rs index f440b2aca..7c9cc7f24 100644 --- a/crates/pop-cli/src/commands/build/mod.rs +++ b/crates/pop-cli/src/commands/build/mod.rs @@ -125,8 +125,8 @@ impl Command { #[cfg(feature = "contract")] if pop_contracts::is_supported(&project_path)? { - let build_mode = contract::resolve_build_mode(&args); - let image = contract::resolve_image(&args)?; + let build_mode = contract::resolve_build_mode(args); + let image = contract::resolve_image(args)?; BuildContract { path: project_path, build_mode, metadata: args.metadata, image } .execute()?; return Ok(()); From 23daed5879e6a22d8bc3228a862199c9c666b0a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Senovilla=20Polo?= Date: Thu, 27 Nov 2025 12:03:08 +0100 Subject: [PATCH 11/18] doc-test --- crates/pop-contracts/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/pop-contracts/README.md b/crates/pop-contracts/README.md index e9ad86841..6454d608b 100644 --- a/crates/pop-contracts/README.md +++ b/crates/pop-contracts/README.md @@ -20,11 +20,11 @@ Build an existing Smart Contract: ```rust,no_run use pop_contracts::build_smart_contract; use std::path::Path; -pub use contract_build::{Verbosity, MetadataSpec}; +pub use contract_build::{Verbosity, MetadataSpec, BuildMode}; let contract_path = Path::new("./"); -let build_release = true; // `true` for release mode, `false` for debug mode. -let result = build_smart_contract(&contract_path, build_release, Verbosity::Default, Some(MetadataSpec::Ink)); // You can build your contract with Solidity metadata using `Some(Metadata::Ink)` +let build_mode = BuildMode::Release; // `true` for release mode, `false` for debug mode, `verifiable` for verifiable contract. +let result = build_smart_contract(&contract_path, build_mode, Verbosity::Default, Some(MetadataSpec::Ink), None); // You can build your contract with Solidity metadata using `Some(Metadata::Solidity)` ``` From 25e97d6e3ffe3a1d0a5b93118edaa3b15178c6c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Senovilla=20Polo?= Date: Thu, 27 Nov 2025 19:36:30 +0100 Subject: [PATCH 12/18] ci: Fix macOS contracts integration test workflows --- .github/workflows/integration-tests.yml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 9e0991e0b..d9bb7871b 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -74,16 +74,19 @@ jobs: - name: Install packages (macOS) if: matrix.os == 'macos-13' run: | - brew install protobuf + brew install protobuf qemu protoc --version - name: Setup Docker (macOS) if: matrix.os == 'macos-13' - uses: docker/setup-docker-action@v4 - - - name: Ensure Docker is running run: | - docker info || (sudo systemctl start docker && docker info) + brew install colima docker + # Use qemu with 9p mount type (more reliable for writes than vz/virtiofs) + colima start --vm-type qemu --mount-type 9p --mount ~:w + # Create symlink to default Docker socket location for compatibility + sudo ln -sf $HOME/.colima/default/docker.sock /var/run/docker.sock + # Verify Docker is working + docker info - name: Install cargo-nextest uses: taiki-e/install-action@v2 @@ -93,7 +96,10 @@ jobs: - name: Run integration tests env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: cargo nextest run --locked --no-default-features --features "contract,integration-tests" --test contract --no-fail-fast --nocapture --status-level all + run: | + # On macOS, Docker can only access certain paths. We use $HOME which is shared with Docker. + [[ "${{ matrix.os }}" == "macos-13" ]] && export TMPDIR=$HOME/tmp && mkdir -p $TMPDIR + cargo nextest run --locked --no-default-features --features "contract,integration-tests" --test contract --no-fail-fast --nocapture --status-level all chain-integration-tests: strategy: From ca08e8d8ec981b6b0555a3135f82ecf1ddb48a88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Senovilla=20Polo?= Date: Fri, 28 Nov 2025 15:21:37 +0100 Subject: [PATCH 13/18] fix: Copilot suggestions --- crates/pop-cli/src/commands/build/contract.rs | 2 +- crates/pop-cli/tests/contract.rs | 3 ++- crates/pop-contracts/README.md | 4 ++-- crates/pop-contracts/src/build.rs | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/pop-cli/src/commands/build/contract.rs b/crates/pop-cli/src/commands/build/contract.rs index e71c83e42..5fe4d8347 100644 --- a/crates/pop-cli/src/commands/build/contract.rs +++ b/crates/pop-cli/src/commands/build/contract.rs @@ -9,7 +9,7 @@ use std::path::PathBuf; pub struct BuildContract { /// Path of the contract project. pub(crate) path: PathBuf, - /// Build profile: `true` for release mode, `false` for debug mode, `verifiable` for + /// Build profile: `Release` for release mode, `Debug` for debug mode, `Verifiable` for /// deterministic, release mode. pub(crate) build_mode: BuildMode, /// Which specification to use for contract metadata. diff --git a/crates/pop-cli/tests/contract.rs b/crates/pop-cli/tests/contract.rs index 22deefcff..fc2fb4a33 100644 --- a/crates/pop-cli/tests/contract.rs +++ b/crates/pop-cli/tests/contract.rs @@ -275,7 +275,8 @@ async fn contract_lifecycle() -> Result<()> { #[tokio::test] async fn verifiable_contract_lifecycle() -> Result<()> { - // TODO: Incomplete test, we'll be adding more steps as the development of the feature advance + // TODO: Incomplete test, we'll be adding more steps as the development of the feature + // progresses let temp = tempfile::tempdir()?; let temp_dir = temp.path(); //let temp_dir = Path::new("./"); //For testing locally diff --git a/crates/pop-contracts/README.md b/crates/pop-contracts/README.md index 6454d608b..2d1cd0632 100644 --- a/crates/pop-contracts/README.md +++ b/crates/pop-contracts/README.md @@ -23,8 +23,8 @@ use std::path::Path; pub use contract_build::{Verbosity, MetadataSpec, BuildMode}; let contract_path = Path::new("./"); -let build_mode = BuildMode::Release; // `true` for release mode, `false` for debug mode, `verifiable` for verifiable contract. -let result = build_smart_contract(&contract_path, build_mode, Verbosity::Default, Some(MetadataSpec::Ink), None); // You can build your contract with Solidity metadata using `Some(Metadata::Solidity)` +let build_mode = BuildMode::Release; // `Release` for release mode, `Debug` for debug mode, `Verifiable` for verifiable contract. +let result = build_smart_contract(&contract_path, build_mode, Verbosity::Default, Some(MetadataSpec::Ink), None); // You can build your contract with Solidity metadata using `Some(MetadataSpec::Solidity)` ``` diff --git a/crates/pop-contracts/src/build.rs b/crates/pop-contracts/src/build.rs index 47798ea8c..1bc8e9fc8 100644 --- a/crates/pop-contracts/src/build.rs +++ b/crates/pop-contracts/src/build.rs @@ -31,7 +31,7 @@ pub fn build_smart_contract( // Execute the build and log the output of the build match build_mode { - // For verifiable contracts, execute calls docker_build (https://github.com/use-ink/cargo-contract/blob/master/crates/build/src/lib.rs#L595) which launchs a blocking tokio runtime to handle the async operations (https://github.com/use-ink/cargo-contract/blob/master/crates/build/src/docker.rs#L135). The issue is that pop is itself a tokio runtime, launching another blocking one isn't allowed by tokio. So for verifiable contracts we need to first block the main pop tokio runtime before calling execute + // For verifiable contracts, execute calls docker_build (https://github.com/use-ink/cargo-contract/blob/master/crates/build/src/lib.rs#L595) which launches a blocking tokio runtime to handle the async operations (https://github.com/use-ink/cargo-contract/blob/master/crates/build/src/docker.rs#L135). The issue is that pop is itself a tokio runtime, launching another blocking one isn't allowed by tokio. So for verifiable contracts we need to first block the main pop tokio runtime before calling execute BuildMode::Verifiable => tokio::task::block_in_place(|| execute(args)), _ => execute(args), } From d765c04038bf143c739dc51221b3967eae20529b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Senovilla=20Polo?= Date: Mon, 1 Dec 2025 11:56:43 +0100 Subject: [PATCH 14/18] ci: Only run verifiable_contract_lifecycle on Ubuntu --- .github/workflows/integration-tests.yml | 36 ++++++++++++------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index d9bb7871b..1a1563088 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -35,7 +35,7 @@ jobs: matrix: os: - ubuntu-latest - - macos-13 + - macos-latest runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -57,7 +57,7 @@ jobs: shared-key: shared-linux-contract${{ github.head_ref || github.ref_name }} - name: Rust Cache MacOS - if: matrix.os == 'macos-13' + if: matrix.os == 'macos-latest' uses: Swatinem/rust-cache@v2 with: cache-on-failure: true @@ -72,22 +72,11 @@ jobs: protoc --version - name: Install packages (macOS) - if: matrix.os == 'macos-13' + if: matrix.os == 'macos-latest' run: | - brew install protobuf qemu + brew install protobuf protoc --version - - name: Setup Docker (macOS) - if: matrix.os == 'macos-13' - run: | - brew install colima docker - # Use qemu with 9p mount type (more reliable for writes than vz/virtiofs) - colima start --vm-type qemu --mount-type 9p --mount ~:w - # Create symlink to default Docker socket location for compatibility - sudo ln -sf $HOME/.colima/default/docker.sock /var/run/docker.sock - # Verify Docker is working - docker info - - name: Install cargo-nextest uses: taiki-e/install-action@v2 with: @@ -97,9 +86,20 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - # On macOS, Docker can only access certain paths. We use $HOME which is shared with Docker. - [[ "${{ matrix.os }}" == "macos-13" ]] && export TMPDIR=$HOME/tmp && mkdir -p $TMPDIR - cargo nextest run --locked --no-default-features --features "contract,integration-tests" --test contract --no-fail-fast --nocapture --status-level all + FILTER="" + if [[ "${{ matrix.os }}" == "macos-latest" ]]; then + FILTER="--filter-expr 'not test(verifiable_contract_lifecycle)'" + fi + + cargo nextest run \ + --locked \ + --no-default-features \ + --features "contract,integration-tests" \ + --test contract \ + --no-fail-fast \ + --nocapture \ + --status-level all \ + $FILTER chain-integration-tests: strategy: From e099c9c140fe3da48099ecf6effbd4da237d1228 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Senovilla=20Polo?= Date: Mon, 1 Dec 2025 11:58:12 +0100 Subject: [PATCH 15/18] chore: Only accept image on verifiable builds --- crates/pop-cli/src/commands/build/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/pop-cli/src/commands/build/mod.rs b/crates/pop-cli/src/commands/build/mod.rs index 7c9cc7f24..52e7ca001 100644 --- a/crates/pop-cli/src/commands/build/mod.rs +++ b/crates/pop-cli/src/commands/build/mod.rs @@ -90,7 +90,7 @@ pub(crate) struct BuildArgs { #[cfg(feature = "contract")] pub(crate) verifiable: bool, /// Custom image for verifiable builds - #[clap(long, help_heading = CONTRACT_HELP_HEADER)] + #[clap(long, requires = "verifiable", help_heading = CONTRACT_HELP_HEADER)] #[cfg(feature = "contract")] pub(crate) image: Option, } From 661a65ffbbe77dea551562dca6e7e6abcb6374ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Senovilla=20Polo?= Date: Mon, 1 Dec 2025 12:17:24 +0100 Subject: [PATCH 16/18] chore: Update temporary cargo contract dependencies to merged commit --- .github/workflows/integration-tests.yml | 2 +- Cargo.toml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 1a1563088..acc8159c2 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -88,7 +88,7 @@ jobs: run: | FILTER="" if [[ "${{ matrix.os }}" == "macos-latest" ]]; then - FILTER="--filter-expr 'not test(verifiable_contract_lifecycle)'" + FILTER="--filter-expr 'not test("verifiable_contract_lifecycle")'" fi cargo nextest run \ diff --git a/Cargo.toml b/Cargo.toml index bc00a5b86..071e0d7a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,9 +57,9 @@ sp-weights = { version = "33.0.0", default-features = false } scale = { package = "parity-scale-codec", version = "3.7.5", features = ["derive"] } scale-info = { version = "2.11.6", default-features = false, features = ["derive"] } scale-value = { version = "0.18.0", default-features = false, features = ["from-string", "parser-ss58"] } -contract-build = { git = "https://github.com/use-ink/cargo-contract", rev = "c7aba2e", default-features = false } -contract-extrinsics = { git = "https://github.com/use-ink/cargo-contract", rev = "c7aba2e", default-features = false } -contract-transcode = { git = "https://github.com/use-ink/cargo-contract", rev = "c7aba2e", default-features = false } +contract-build = { git = "https://github.com/use-ink/cargo-contract", rev = "ceb7b89", default-features = false } +contract-extrinsics = { git = "https://github.com/use-ink/cargo-contract", rev = "ceb7b89", default-features = false } +contract-transcode = { git = "https://github.com/use-ink/cargo-contract", rev = "ceb7b89", default-features = false } heck = { version = "0.5.0", default-features = false } # parachains From 8b5ca42ae0582d7653b58eb98a24d47f3d4f599b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Senovilla=20Polo?= Date: Mon, 1 Dec 2025 12:22:37 +0100 Subject: [PATCH 17/18] chore: Update manifest --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e68d97a0a..1f23a75b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2098,7 +2098,7 @@ checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "contract-build" version = "6.0.0-beta.1" -source = "git+https://github.com/use-ink/cargo-contract?rev=c7aba2e#c7aba2e05474bd5dd70d7ab81195cab2994710d6" +source = "git+https://github.com/use-ink/cargo-contract?rev=ceb7b89#ceb7b89f807b0e446dce68c007511d5dbc171277" dependencies = [ "alloy-json-abi", "anyhow", @@ -2141,7 +2141,7 @@ dependencies = [ [[package]] name = "contract-extrinsics" version = "6.0.0-beta.1" -source = "git+https://github.com/use-ink/cargo-contract?rev=c7aba2e#c7aba2e05474bd5dd70d7ab81195cab2994710d6" +source = "git+https://github.com/use-ink/cargo-contract?rev=ceb7b89#ceb7b89f807b0e446dce68c007511d5dbc171277" dependencies = [ "anyhow", "colored", @@ -2171,7 +2171,7 @@ dependencies = [ [[package]] name = "contract-metadata" version = "6.0.0-beta.1" -source = "git+https://github.com/use-ink/cargo-contract?rev=c7aba2e#c7aba2e05474bd5dd70d7ab81195cab2994710d6" +source = "git+https://github.com/use-ink/cargo-contract?rev=ceb7b89#ceb7b89f807b0e446dce68c007511d5dbc171277" dependencies = [ "anyhow", "impl-serde", @@ -2184,7 +2184,7 @@ dependencies = [ [[package]] name = "contract-transcode" version = "6.0.0-beta.1" -source = "git+https://github.com/use-ink/cargo-contract?rev=c7aba2e#c7aba2e05474bd5dd70d7ab81195cab2994710d6" +source = "git+https://github.com/use-ink/cargo-contract?rev=ceb7b89#ceb7b89f807b0e446dce68c007511d5dbc171277" dependencies = [ "anyhow", "base58", From 0bf020282c550411d44641b7b8591f4595e7159d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Senovilla=20Polo?= Date: Mon, 1 Dec 2025 12:43:23 +0100 Subject: [PATCH 18/18] ci: Fix skipping test --- .github/workflows/integration-tests.yml | 30 +++++++++++++++---------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index acc8159c2..85349403b 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -86,21 +86,27 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - FILTER="" if [[ "${{ matrix.os }}" == "macos-latest" ]]; then - FILTER="--filter-expr 'not test("verifiable_contract_lifecycle")'" + cargo nextest run \ + --locked \ + --no-default-features \ + --features "contract,integration-tests" \ + --test contract \ + --no-fail-fast \ + --nocapture \ + --status-level all \ + -- --skip verifiable_contract_lifecycle + else + cargo nextest run \ + --locked \ + --no-default-features \ + --features "contract,integration-tests" \ + --test contract \ + --no-fail-fast \ + --nocapture \ + --status-level all fi - cargo nextest run \ - --locked \ - --no-default-features \ - --features "contract,integration-tests" \ - --test contract \ - --no-fail-fast \ - --nocapture \ - --status-level all \ - $FILTER - chain-integration-tests: strategy: matrix: