Repository: OffchainLabs/cargo-stylus (language: rust)

Issue:
Add cargo stylus version to the name of created docker image
Currently created docker image name includes only the version of the rust toolchain and does not include the version of the cargo stylus package (`CARGO_PKG_VERSION`) - https://github.com/OffchainLabs/cargo-stylus/blob/main/check/src/docker.rs#L44-L47

That may result in the following scenario when next versions of the cargo stylus are released:
1. A user has used `cargo stylus 0.4.2` with rust-toolchain 1.80.0, deployed some contracts via that setup and has `cargo-stylus-1.80.0` image installed locally.
2. The user updates cargo stylus to v0.5.0
3. Deploys a new contract with new `v0.5.0` version. The code then just checks if `cargo-stylus-1.80.0` image exists locally (https://github.com/OffchainLabs/cargo-stylus/blob/main/check/src/docker.rs#L45) and as it already does, it uses that image that internally has `cargo stylus 0.4.2` installed.
4. When trying to verify the contract, the user believes that v0.5.0 version was used, while in reality it was v0.4.2

### Solution
I suggest to add a current cargo stylus version in the docker image name as well - e.g. `cargo-stylus-0.4.2-1.80.0`


Discussion / hints:
Hi, @rauljordan 

I don't think https://github.com/OffchainLabs/cargo-stylus/pull/82 closed this issue. The problem was related to the fact that cargo stylus version was not used in the generated docker image name, and it still does not. Actually, with `cargo-stylus v0.5.0` being released, you can reproduce that issue in practice:

1. Start with `cargo-stylus v0.4.2` (`cargo install cargo-stylus-check --version 0.4.2 --force` and `cargo install cargo-stylus --version 0.4.2 --force`)
2. Initialize a new cargo stylus project (`cargo stylus new`)
3. Deploy a stylus-hello example via reproducible build (`cargo stylus deploy --private-key=XXX`). That step build and save `cargo-stylus-1.79.0` image on your local machine
4. Uninstall cargo-stylus v0.4.2 and install v0.5.0 instead (`cargo uninstall cargo-stylus`, `cargo uninstall cargo-stylus-check`, `cargo install cargo-stylus --version 0.5.0 --force`)
5. Deploy the same stylus-hello example (`cargo stylus deploy --private-key=XXX`). You can notice that no new image has been build and cargo-stylus v0.4.2 was still used

Fix patch:
diff --git a/main/src/docker.rs b/main/src/docker.rs
index c7b430e..0b892f6 100644
--- a/main/src/docker.rs
+++ b/main/src/docker.rs
@@ -12,14 +12,11 @@ use crate::constants::TOOLCHAIN_FILE_NAME;
 use crate::macros::greyln;
 use crate::project::extract_toolchain_channel;
 
-fn version_to_image_name(version: &str) -> String {
-    format!("cargo-stylus-{}", version)
-}
-
-fn image_exists(name: &str) -> Result<bool> {
+fn image_exists() -> Result<bool> {
+    let image_name = format!("cargo-stylus-base:{}", env!("CARGO_PKG_VERSION"));
     let output = Command::new("docker")
         .arg("images")
-        .arg(name)
+        .arg(image_name)
         .output()
         .map_err(|e| eyre!("failed to execute Docker command: {e}"))?;
 
@@ -41,10 +38,11 @@ a reproducible deployment, or opt out by using the --no-verify flag for local bu
 }
 
 fn create_image(version: &str) -> Result<()> {
-    let name = version_to_image_name(version);
-    if image_exists(&name)? {
+    if image_exists()? {
         return Ok(());
     }
+    let pkg_version = env!("CARGO_PKG_VERSION");
+    let name = format!("cargo-stylus-base-{}-toolchain-{}", pkg_version, version);
     println!("Building Docker image for Rust toolchain {}", version,);
     let mut child = Command::new("docker")
         .arg("build")
@@ -58,12 +56,13 @@ fn create_image(version: &str) -> Result<()> {
     write!(
         child.stdin.as_mut().unwrap(),
         "\
-            FROM --platform=linux/amd64 offchainlabs/cargo-stylus-base:0.5.0 as base
+            FROM --platform=linux/amd64 offchainlabs/cargo-stylus-base:{} as base
             RUN rustup toolchain install {}-x86_64-unknown-linux-gnu 
             RUN rustup default {}-x86_64-unknown-linux-gnu
             RUN rustup target add wasm32-unknown-unknown
             RUN rustup component add rust-src --toolchain {}-x86_64-unknown-linux-gnu
         ",
+        pkg_version,
         version,
         version,
         version,
@@ -73,10 +72,8 @@ fn create_image(version: &str) -> Result<()> {
 }
 
 fn run_in_docker_container(version: &str, command_line: &[&str]) -> Result<()> {
-    let name = version_to_image_name(version);
-    if !image_exists(&name)? {
-        bail!("Docker image {name} doesn't exist");
-    }
+    let pkg_version = env!("CARGO_PKG_VERSION");
+    let name = format!("cargo-stylus-base-{}-toolchain-{}", pkg_version, version);
     let dir =
         std::env::current_dir().map_err(|e| eyre!("failed to find current directory: {e}"))?;
     Command::new("docker")
diff --git a/main/src/main.rs b/main/src/main.rs
index 9c4843c..ea2bd59 100644
--- a/main/src/main.rs
+++ b/main/src/main.rs
@@ -604,6 +604,7 @@ pub fn build_so(path: &Path) -> Result<()> {
         .current_dir(path)
         .arg("build")
         .arg("--lib")
+        .arg("--locked")
         .arg("--target")
         .arg(rustc_host::from_cli()?)
         .output()?;
diff --git a/main/src/project.rs b/main/src/project.rs
index 092692e..0f0fd4e 100644
--- a/main/src/project.rs
+++ b/main/src/project.rs
@@ -59,8 +59,14 @@ pub fn build_dylib(cfg: BuildConfig) -> Result<PathBuf> {
 
     let mut cmd = sys::new_command("cargo");
 
+    // Enforce a version is included in the Cargo.toml file.
+    let cargo_toml_path = cwd.join(Path::new("Cargo.toml"));
+    let cargo_toml_version = extract_cargo_toml_version(&cargo_toml_path)?;
+    greyln!("Building project with Cargo.toml version: {cargo_toml_version}");
+
     cmd.arg("build");
     cmd.arg("--lib");
+    cmd.arg("--locked");
 
     if !cfg.stable {
         cmd.arg("-Z");
@@ -203,6 +209,25 @@ pub fn extract_toolchain_channel(toolchain_file_path: &PathBuf) -> Result<String
     Ok(channel)
 }
 
+pub fn extract_cargo_toml_version(cargo_toml_path: &PathBuf) -> Result<String> {
+    let cargo_toml_contents = fs::read_to_string(cargo_toml_path)
+        .context("expected to find a Cargo.toml file in project directory")?;
+
+    let cargo_toml: Value =
+        toml::from_str(&cargo_toml_contents).context("failed to parse Cargo.toml")?;
+
+    let Some(pkg) = cargo_toml.get("package") else {
+        bail!("package section not found in Cargo.toml");
+    };
+    let Some(version) = pkg.get("version") else {
+        bail!("could not find version in project's Cargo.toml [package] section");
+    };
+    let Some(version) = version.as_str() else {
+        bail!("version in Cargo.toml's [package] section is not a string");
+    };
+    Ok(version.to_string())
+}
+
 pub fn hash_files(source_file_patterns: Vec<String>, cfg: BuildConfig) -> Result<[u8; 32]> {
     let mut keccak = Keccak::v256();
     let mut cmd = Command::new("cargo");


Test patch: