From f1ae43c4fa6c7690af0a4cfb5760bf5321e9dfe3 Mon Sep 17 00:00:00 2001 From: Aumetra Weisman Date: Thu, 25 Apr 2024 13:47:44 +0200 Subject: [PATCH 1/5] Parse the contract state version from the custom section --- Cargo.lock | 12 ++++++++++- packages/vm/Cargo.toml | 1 + packages/vm/src/cache.rs | 26 ++++++++++++++++++++++++ packages/vm/src/parsed_wasm.rs | 37 +++++++++++++++++++++++++++++++++- 4 files changed, 74 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e1a4b08833..6dc054f946 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -544,6 +544,7 @@ dependencies = [ "thiserror", "time", "tracing", + "wasm-encoder 0.205.0", "wasmer", "wasmer-middlewares", "wat", @@ -2491,6 +2492,15 @@ dependencies = [ "leb128", ] +[[package]] +name = "wasm-encoder" +version = "0.205.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e95b3563d164f33c1cfb0a7efbd5940c37710019be10cd09f800fdec8b0e5c" +dependencies = [ + "leb128", +] + [[package]] name = "wasmer" version = "4.2.6" @@ -2672,7 +2682,7 @@ dependencies = [ "leb128", "memchr", "unicode-width", - "wasm-encoder", + "wasm-encoder 0.27.0", ] [[package]] diff --git a/packages/vm/Cargo.toml b/packages/vm/Cargo.toml index f140107243..50860ed16e 100644 --- a/packages/vm/Cargo.toml +++ b/packages/vm/Cargo.toml @@ -79,6 +79,7 @@ hex-literal = "0.4.1" rand = "0.8" tempfile = "3.1.0" wat = "1.0" +wasm-encoder = "0.205.0" clap = "4" leb128 = "0.2" target-lexicon = "0.12" diff --git a/packages/vm/src/cache.rs b/packages/vm/src/cache.rs index 2810e83683..1393fe7462 100644 --- a/packages/vm/src/cache.rs +++ b/packages/vm/src/cache.rs @@ -132,6 +132,8 @@ pub struct AnalysisReport { pub entrypoints: BTreeSet, /// The set of capabilities the contract requires. pub required_capabilities: BTreeSet, + /// The contract state version exported set by the contract developer + pub contract_state_version: Option, } impl Cache @@ -320,6 +322,7 @@ where required_capabilities: required_capabilities_from_module(&module) .into_iter() .collect(), + contract_state_version: module.contract_state_version, }) } @@ -584,6 +587,7 @@ mod tests { use cosmwasm_std::{coins, Empty}; use std::fs::{create_dir_all, remove_dir_all}; use tempfile::TempDir; + use wasm_encoder::ComponentSection; const TESTING_GAS_LIMIT: u64 = 500_000_000; // ~0.5ms const TESTING_MEMORY_LIMIT: Size = Size::mebi(16); @@ -1410,6 +1414,7 @@ mod tests { E::Query ]), required_capabilities: BTreeSet::new(), + contract_state_version: None, } ); @@ -1427,6 +1432,7 @@ mod tests { "iterator".to_string(), "stargate".to_string() ]), + contract_state_version: None, } ); @@ -1438,6 +1444,26 @@ mod tests { has_ibc_entry_points: false, entrypoints: BTreeSet::new(), required_capabilities: BTreeSet::from(["iterator".to_string()]), + contract_state_version: None, + } + ); + + let mut wasm_with_version = EMPTY_CONTRACT.to_vec(); + let custom_section = wasm_encoder::CustomSection { + name: "cw_state_version".into(), + data: b"21".into(), + }; + custom_section.append_to_component(&mut wasm_with_version); + + let checksum4 = cache.save_wasm(&wasm_with_version).unwrap(); + let report4 = cache.analyze(&checksum4).unwrap(); + assert_eq!( + report4, + AnalysisReport { + has_ibc_entry_points: false, + entrypoints: BTreeSet::new(), + required_capabilities: BTreeSet::from(["iterator".to_string()]), + contract_state_version: Some(21), } ); } diff --git a/packages/vm/src/parsed_wasm.rs b/packages/vm/src/parsed_wasm.rs index 757852bd7d..8324dd6c89 100644 --- a/packages/vm/src/parsed_wasm.rs +++ b/packages/vm/src/parsed_wasm.rs @@ -1,4 +1,4 @@ -use std::{fmt, mem}; +use std::{fmt, mem, str}; use wasmer::wasmparser::{ BinaryReaderError, CompositeType, Export, FuncToValidate, FunctionBody, Import, MemoryType, @@ -66,6 +66,8 @@ pub struct ParsedWasm<'a> { pub total_func_params: usize, /// Collections of functions that are potentially pending validation pub func_validator: FunctionValidator<'a>, + /// Contract state version as defined in a custom section + pub contract_state_version: Option, } impl<'a> ParsedWasm<'a> { @@ -108,6 +110,7 @@ impl<'a> ParsedWasm<'a> { max_func_results: 0, total_func_params: 0, func_validator: FunctionValidator::Pending(OpaqueDebug::default()), + contract_state_version: None, }; for p in Parser::new(0).parse_all(wasm) { @@ -179,6 +182,17 @@ impl<'a> ParsedWasm<'a> { Payload::ExportSection(e) => { this.exports = e.into_iter().collect::, _>>()?; } + Payload::CustomSection(reader) if reader.name() == "cw_state_version" => { + // This is supposed to be valid UTF-8 + let raw_version = str::from_utf8(reader.data()) + .map_err(|err| VmError::parse_err("str", err))?; + + this.contract_state_version = Some( + raw_version + .parse() + .map_err(|err| VmError::parse_err("u64", err))?, + ); + } _ => {} // ignore everything else } } @@ -214,3 +228,24 @@ impl<'a> ParsedWasm<'a> { } } } + +#[cfg(test)] +mod test { + use super::ParsedWasm; + + #[test] + fn read_state_version() { + let wasm_data = + wat::parse_str(r#"( module ( @custom "cw_state_version" "42" ) )"#).unwrap(); + let parsed = ParsedWasm::parse(&wasm_data).unwrap(); + + assert_eq!(parsed.contract_state_version, Some(42)); + } + + #[test] + fn read_state_version_fails() { + let wasm_data = + wat::parse_str(r#"( module ( @custom "cw_state_version" "not a number" ) )"#).unwrap(); + assert!(ParsedWasm::parse(&wasm_data).is_err()); + } +} From 71c111342a56a1a499fc1337866d500cd47802fc Mon Sep 17 00:00:00 2001 From: Aumetra Weisman Date: Thu, 25 Apr 2024 13:50:49 +0200 Subject: [PATCH 2/5] Explicitly use Cow --- packages/vm/src/cache.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/vm/src/cache.rs b/packages/vm/src/cache.rs index 1393fe7462..3f9aad0baa 100644 --- a/packages/vm/src/cache.rs +++ b/packages/vm/src/cache.rs @@ -585,6 +585,7 @@ mod tests { use crate::capabilities::capabilities_from_csv; use crate::testing::{mock_backend, mock_env, mock_info, MockApi, MockQuerier, MockStorage}; use cosmwasm_std::{coins, Empty}; + use std::borrow::Cow; use std::fs::{create_dir_all, remove_dir_all}; use tempfile::TempDir; use wasm_encoder::ComponentSection; @@ -1450,8 +1451,8 @@ mod tests { let mut wasm_with_version = EMPTY_CONTRACT.to_vec(); let custom_section = wasm_encoder::CustomSection { - name: "cw_state_version".into(), - data: b"21".into(), + name: Cow::Borrowed("cw_state_version"), + data: Cow::Borrowed(b"21"), }; custom_section.append_to_component(&mut wasm_with_version); From 2b4779f401dde472b7fb7961f8c3874256224ad1 Mon Sep 17 00:00:00 2001 From: Aumetra Weisman Date: Thu, 25 Apr 2024 15:57:25 +0200 Subject: [PATCH 3/5] Use `static_validation_err`, mark struct as non-exhaustive --- packages/vm/src/cache.rs | 1 + packages/vm/src/parsed_wasm.rs | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/vm/src/cache.rs b/packages/vm/src/cache.rs index 3f9aad0baa..7a6e41a838 100644 --- a/packages/vm/src/cache.rs +++ b/packages/vm/src/cache.rs @@ -124,6 +124,7 @@ pub struct Cache { } #[derive(PartialEq, Eq, Debug)] +#[non_exhaustive] pub struct AnalysisReport { /// `true` if and only if all [`REQUIRED_IBC_EXPORTS`] exist as exported functions. /// This does not guarantee they are functional or even have the correct signatures. diff --git a/packages/vm/src/parsed_wasm.rs b/packages/vm/src/parsed_wasm.rs index 8324dd6c89..3d428ba7a0 100644 --- a/packages/vm/src/parsed_wasm.rs +++ b/packages/vm/src/parsed_wasm.rs @@ -185,12 +185,12 @@ impl<'a> ParsedWasm<'a> { Payload::CustomSection(reader) if reader.name() == "cw_state_version" => { // This is supposed to be valid UTF-8 let raw_version = str::from_utf8(reader.data()) - .map_err(|err| VmError::parse_err("str", err))?; + .map_err(|err| VmError::static_validation_err(err.to_string()))?; this.contract_state_version = Some( raw_version - .parse() - .map_err(|err| VmError::parse_err("u64", err))?, + .parse::() + .map_err(|err| VmError::static_validation_err(err.to_string()))?, ); } _ => {} // ignore everything else From 46cc27baf807ebf201192c516e492b2fe9eac041 Mon Sep 17 00:00:00 2001 From: Aumetra Weisman Date: Thu, 25 Apr 2024 16:20:28 +0200 Subject: [PATCH 4/5] Add Changelog entry --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1abc85d29..18e64d7a6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,8 @@ and this project adheres to ([#2120]) - cosmwasm-derive: Add `state_version` attribute for `migrate` entrypoints ([#2124]) +- cosmwasm-vm: Read the state version from WASM modules and return them as part + of `AnalyzeReport` ([#2129]) [#1983]: https://github.com/CosmWasm/cosmwasm/pull/1983 [#2057]: https://github.com/CosmWasm/cosmwasm/pull/2057 @@ -47,6 +49,7 @@ and this project adheres to [#2107]: https://github.com/CosmWasm/cosmwasm/pull/2107 [#2120]: https://github.com/CosmWasm/cosmwasm/pull/2120 [#2124]: https://github.com/CosmWasm/cosmwasm/pull/2124 +[#2129]: https://github.com/CosmWasm/cosmwasm/pull/2129 ### Changed From 693610f1c704fbbb4a5a506ff57c7159bb880bce Mon Sep 17 00:00:00 2001 From: Aumetra Weisman Date: Thu, 25 Apr 2024 17:12:46 +0200 Subject: [PATCH 5/5] Update CHANGELOG.md Co-authored-by: Simon Warta <2603011+webmaster128@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18e64d7a6e..8f0982dfec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,7 +35,7 @@ and this project adheres to ([#2120]) - cosmwasm-derive: Add `state_version` attribute for `migrate` entrypoints ([#2124]) -- cosmwasm-vm: Read the state version from WASM modules and return them as part +- cosmwasm-vm: Read the state version from Wasm modules and return them as part of `AnalyzeReport` ([#2129]) [#1983]: https://github.com/CosmWasm/cosmwasm/pull/1983