diff --git a/CHANGELOG.md b/CHANGELOG.md index d409f023f1..0eb9f1ba98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,26 @@ and this project adheres to ## [Unreleased] +### Added + +- cosmwasm-std: Add `FromStr` impl for `Coin`. ([#1684]) + +[#1684]: https://github.com/CosmWasm/cosmwasm/pull/1684 + +### Changed + +- cosmwasm-vm: Add checks for table section of Wasm blob ([#1631]). +- cosmwasm-vm: Limit number of imports during static validation ([#1629]). +- cosmwasm-vm: Add target (triple + CPU features) into the module cache + directory to avoid using modules compiled for a different system. Bump + `MODULE_SERIALIZATION_VERSION` to "v6". ([#1664]) +- cosmwasm-vm: Add `.wasm` extension to stored wasm files ([#1686]). + +[#1629]: https://github.com/CosmWasm/cosmwasm/pull/1629 +[#1631]: https://github.com/CosmWasm/cosmwasm/pull/1631 +[#1664]: https://github.com/CosmWasm/cosmwasm/pull/1664 +[#1686]: https://github.com/CosmWasm/cosmwasm/pull/1686 + ## [1.2.7] - 2023-06-19 ### Added diff --git a/Cargo.lock b/Cargo.lock index c028d0028e..2a7dafafed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -385,8 +385,10 @@ dependencies = [ "clru", "cosmwasm-crypto", "cosmwasm-std", + "crc32fast", "criterion", "enumset", + "glob", "hex", "hex-literal", "leb128", @@ -397,6 +399,7 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.3", + "target-lexicon", "tempfile", "thiserror", "wasmer", @@ -890,6 +893,12 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "group" version = "0.12.0" @@ -1190,9 +1199,9 @@ checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" [[package]] name = "parity-wasm" -version = "0.42.2" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" +checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" [[package]] name = "pin-project-lite" diff --git a/contracts/burner/Cargo.lock b/contracts/burner/Cargo.lock index 9d00c76f9a..ffc5bc3faf 100644 --- a/contracts/burner/Cargo.lock +++ b/contracts/burner/Cargo.lock @@ -237,6 +237,7 @@ dependencies = [ "clru", "cosmwasm-crypto", "cosmwasm-std", + "crc32fast", "enumset", "hex", "loupe", @@ -329,9 +330,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if", ] @@ -949,9 +950,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "parity-wasm" -version = "0.42.2" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" +checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" [[package]] name = "pin-project-lite" diff --git a/contracts/crypto-verify/Cargo.lock b/contracts/crypto-verify/Cargo.lock index 70de208acd..525807cef8 100644 --- a/contracts/crypto-verify/Cargo.lock +++ b/contracts/crypto-verify/Cargo.lock @@ -240,6 +240,7 @@ dependencies = [ "clru", "cosmwasm-crypto", "cosmwasm-std", + "crc32fast", "enumset", "hex", "loupe", @@ -332,9 +333,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if", ] @@ -981,9 +982,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "parity-wasm" -version = "0.42.2" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" +checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" [[package]] name = "pin-project-lite" diff --git a/contracts/cyberpunk/Cargo.lock b/contracts/cyberpunk/Cargo.lock index 530f7005ea..b254a657ac 100644 --- a/contracts/cyberpunk/Cargo.lock +++ b/contracts/cyberpunk/Cargo.lock @@ -263,6 +263,7 @@ dependencies = [ "clru", "cosmwasm-crypto", "cosmwasm-std", + "crc32fast", "enumset", "hex", "loupe", @@ -355,9 +356,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if", ] @@ -987,9 +988,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "parity-wasm" -version = "0.42.2" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" +checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" [[package]] name = "pin-project-lite" diff --git a/contracts/floaty/Cargo.lock b/contracts/floaty/Cargo.lock index 32c1428dc9..7903f0555d 100644 --- a/contracts/floaty/Cargo.lock +++ b/contracts/floaty/Cargo.lock @@ -234,6 +234,7 @@ dependencies = [ "clru", "cosmwasm-crypto", "cosmwasm-std", + "crc32fast", "enumset", "hex", "loupe", @@ -326,9 +327,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if", ] @@ -959,9 +960,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "parity-wasm" -version = "0.42.2" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" +checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" [[package]] name = "pin-project-lite" diff --git a/contracts/hackatom/Cargo.lock b/contracts/hackatom/Cargo.lock index dd9f21a4da..77eb0d0e6d 100644 --- a/contracts/hackatom/Cargo.lock +++ b/contracts/hackatom/Cargo.lock @@ -234,6 +234,7 @@ dependencies = [ "clru", "cosmwasm-crypto", "cosmwasm-std", + "crc32fast", "enumset", "hex", "loupe", @@ -326,9 +327,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if", ] @@ -960,9 +961,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "parity-wasm" -version = "0.42.2" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" +checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" [[package]] name = "pin-project-lite" diff --git a/contracts/ibc-reflect-send/Cargo.lock b/contracts/ibc-reflect-send/Cargo.lock index c428c1e7a9..68f519d999 100644 --- a/contracts/ibc-reflect-send/Cargo.lock +++ b/contracts/ibc-reflect-send/Cargo.lock @@ -234,6 +234,7 @@ dependencies = [ "clru", "cosmwasm-crypto", "cosmwasm-std", + "crc32fast", "enumset", "hex", "loupe", @@ -326,9 +327,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if", ] @@ -958,9 +959,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "parity-wasm" -version = "0.42.2" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" +checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" [[package]] name = "pin-project-lite" diff --git a/contracts/ibc-reflect/Cargo.lock b/contracts/ibc-reflect/Cargo.lock index bf6645c38d..06dcdd37bc 100644 --- a/contracts/ibc-reflect/Cargo.lock +++ b/contracts/ibc-reflect/Cargo.lock @@ -234,6 +234,7 @@ dependencies = [ "clru", "cosmwasm-crypto", "cosmwasm-std", + "crc32fast", "enumset", "hex", "loupe", @@ -326,9 +327,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if", ] @@ -958,9 +959,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "parity-wasm" -version = "0.42.2" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" +checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" [[package]] name = "pin-project-lite" diff --git a/contracts/queue/Cargo.lock b/contracts/queue/Cargo.lock index 82597f4ea7..ec0086d567 100644 --- a/contracts/queue/Cargo.lock +++ b/contracts/queue/Cargo.lock @@ -226,6 +226,7 @@ dependencies = [ "clru", "cosmwasm-crypto", "cosmwasm-std", + "crc32fast", "enumset", "hex", "loupe", @@ -318,9 +319,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if", ] @@ -938,9 +939,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "parity-wasm" -version = "0.42.2" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" +checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" [[package]] name = "pin-project-lite" diff --git a/contracts/reflect/Cargo.lock b/contracts/reflect/Cargo.lock index d462e18fdd..90a729bb0d 100644 --- a/contracts/reflect/Cargo.lock +++ b/contracts/reflect/Cargo.lock @@ -234,6 +234,7 @@ dependencies = [ "clru", "cosmwasm-crypto", "cosmwasm-std", + "crc32fast", "enumset", "hex", "loupe", @@ -326,9 +327,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if", ] @@ -946,9 +947,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "parity-wasm" -version = "0.42.2" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" +checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" [[package]] name = "pin-project-lite" diff --git a/contracts/staking/Cargo.lock b/contracts/staking/Cargo.lock index f63bd2552a..7d8e834d27 100644 --- a/contracts/staking/Cargo.lock +++ b/contracts/staking/Cargo.lock @@ -234,6 +234,7 @@ dependencies = [ "clru", "cosmwasm-crypto", "cosmwasm-std", + "crc32fast", "enumset", "hex", "loupe", @@ -326,9 +327,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if", ] @@ -952,9 +953,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "parity-wasm" -version = "0.42.2" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" +checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" [[package]] name = "pin-project-lite" diff --git a/contracts/virus/Cargo.lock b/contracts/virus/Cargo.lock index ff161c069d..a55dc46412 100644 --- a/contracts/virus/Cargo.lock +++ b/contracts/virus/Cargo.lock @@ -226,6 +226,7 @@ dependencies = [ "clru", "cosmwasm-crypto", "cosmwasm-std", + "crc32fast", "enumset", "hex", "loupe", @@ -318,9 +319,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if", ] @@ -938,9 +939,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "parity-wasm" -version = "0.42.2" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" +checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" [[package]] name = "pin-project-lite" diff --git a/packages/std/src/coin.rs b/packages/std/src/coin.rs index af14644ae5..13a74e0f4b 100644 --- a/packages/std/src/coin.rs +++ b/packages/std/src/coin.rs @@ -1,8 +1,8 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use std::fmt; +use std::{fmt, str::FromStr}; -use crate::math::Uint128; +use crate::{errors::CoinFromStrError, math::Uint128}; #[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq, JsonSchema)] pub struct Coin { @@ -25,6 +25,26 @@ impl fmt::Debug for Coin { } } +impl FromStr for Coin { + type Err = CoinFromStrError; + + fn from_str(s: &str) -> Result { + let pos = s + .find(|c: char| !c.is_ascii_digit()) + .ok_or(CoinFromStrError::MissingDenom)?; + let (amount, denom) = s.split_at(pos); + + if amount.is_empty() { + return Err(CoinFromStrError::MissingAmount); + } + + Ok(Coin { + amount: amount.parse::()?.into(), + denom: denom.to_string(), + }) + } +} + impl fmt::Display for Coin { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // We use the formatting without a space between amount and denom, @@ -178,4 +198,53 @@ mod tests { let coin = Coin::new(123, "ucosm"); assert_eq!(format!("{:?}", coin), r#"Coin { 123 "ucosm" }"#); } + + #[test] + fn parse_coin() { + let expected = Coin::new(123, "ucosm"); + assert_eq!("123ucosm".parse::().unwrap(), expected); + // leading zeroes should be ignored + assert_eq!("00123ucosm".parse::().unwrap(), expected); + // 0 amount parses correctly + assert_eq!("0ucosm".parse::().unwrap(), Coin::new(0, "ucosm")); + // ibc denom should work + let ibc_str = "11111ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2"; + let ibc_coin = Coin::new( + 11111, + "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2", + ); + assert_eq!(ibc_str.parse::().unwrap(), ibc_coin); + + // error cases + assert_eq!( + Coin::from_str("123").unwrap_err(), + CoinFromStrError::MissingDenom + ); + assert_eq!( + Coin::from_str("ucosm").unwrap_err(), // no amount + CoinFromStrError::MissingAmount + ); + assert_eq!( + Coin::from_str("-123ucosm").unwrap_err(), // negative amount + CoinFromStrError::MissingAmount + ); + assert_eq!( + Coin::from_str("").unwrap_err(), // empty input + CoinFromStrError::MissingDenom + ); + assert_eq!( + Coin::from_str(" 1ucosm").unwrap_err(), // unsupported whitespace + CoinFromStrError::MissingAmount + ); + assert_eq!( + Coin::from_str("�1ucosm").unwrap_err(), // other broken data + CoinFromStrError::MissingAmount + ); + assert_eq!( + Coin::from_str("340282366920938463463374607431768211456ucosm") + .unwrap_err() + .to_string(), + "Invalid amount: number too large to fit in target type" + ); + } } diff --git a/packages/std/src/errors/mod.rs b/packages/std/src/errors/mod.rs index 705382b732..5535479bdf 100644 --- a/packages/std/src/errors/mod.rs +++ b/packages/std/src/errors/mod.rs @@ -6,7 +6,7 @@ mod verification_error; pub use recover_pubkey_error::RecoverPubkeyError; pub use std_error::{ CheckedFromRatioError, CheckedMultiplyFractionError, CheckedMultiplyRatioError, - ConversionOverflowError, DivideByZeroError, OverflowError, OverflowOperation, + CoinFromStrError, ConversionOverflowError, DivideByZeroError, OverflowError, OverflowOperation, RoundUpOverflowError, StdError, StdResult, }; pub use system_error::SystemError; diff --git a/packages/std/src/errors/std_error.rs b/packages/std/src/errors/std_error.rs index 79b6a82f27..d90171c74c 100644 --- a/packages/std/src/errors/std_error.rs +++ b/packages/std/src/errors/std_error.rs @@ -590,6 +590,28 @@ pub enum CheckedFromRatioError { #[error("Round up operation failed because of overflow")] pub struct RoundUpOverflowError; +#[derive(Error, Debug, PartialEq, Eq)] +pub enum CoinFromStrError { + #[error("Missing denominator")] + MissingDenom, + #[error("Missing amount or non-digit characters in amount")] + MissingAmount, + #[error("Invalid amount: {0}")] + InvalidAmount(std::num::ParseIntError), +} + +impl From for CoinFromStrError { + fn from(value: std::num::ParseIntError) -> Self { + Self::InvalidAmount(value) + } +} + +impl From for StdError { + fn from(value: CoinFromStrError) -> Self { + Self::generic_err(format!("Parsing Coin: {}", value)) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/packages/vm/Cargo.toml b/packages/vm/Cargo.toml index 27bef52a5e..59817c55dd 100644 --- a/packages/vm/Cargo.toml +++ b/packages/vm/Cargo.toml @@ -40,11 +40,12 @@ required-features = ["iterator"] [dependencies] clru = "0.4.0" +crc32fast = "1.3.2" # Uses the path when built locally; uses the given version from crates.io when published cosmwasm-std = { path = "../std", version = "1.2.7", default-features = false } cosmwasm-crypto = { path = "../crypto", version = "1.2.7" } hex = "0.4" -parity-wasm = "0.42" +parity-wasm = "0.45" schemars = "0.8.3" serde = { version = "1.0.103", default-features = false, features = ["derive", "alloc"] } serde_json = "1.0.40" @@ -68,12 +69,14 @@ bitflags = "1.1.0" # https://github.com/CensoredUsername/dynasm-rs/pull/74 [dev-dependencies] criterion = { version = "0.4", features = [ "html_reports" ] } +glob = "0.3.1" hex-literal = "0.3.1" tempfile = "3.1.0" wat = "1.0" clap = "2.33.3" rand = "0.8" leb128 = "0.2" +target-lexicon = "0.12" [[bench]] name = "main" diff --git a/packages/vm/src/cache.rs b/packages/vm/src/cache.rs index 36c18a6ad1..43f580cdc5 100644 --- a/packages/vm/src/cache.rs +++ b/packages/vm/src/cache.rs @@ -364,7 +364,7 @@ fn save_wasm_to_disk(dir: impl Into, wasm: &[u8]) -> VmResult // calculate filename let checksum = Checksum::generate(wasm); let filename = checksum.to_hex(); - let filepath = dir.into().join(filename); + let filepath = dir.into().join(filename).with_extension("wasm"); // write data to file // Since the same filename (a collision resistent hash) cannot be generated from two different byte codes @@ -382,9 +382,12 @@ fn save_wasm_to_disk(dir: impl Into, wasm: &[u8]) -> VmResult fn load_wasm_from_disk(dir: impl Into, checksum: &Checksum) -> VmResult> { // this requires the directory and file to exist + // The files previously had no extension, so to allow for a smooth transition, + // we also try to load the file without the wasm extension. let path = dir.into().join(checksum.to_hex()); - let mut file = - File::open(path).map_err(|_e| VmError::cache_err("Error opening Wasm file for reading"))?; + let mut file = File::open(path.with_extension("wasm")) + .or_else(|_| File::open(path)) + .map_err(|_e| VmError::cache_err("Error opening Wasm file for reading"))?; let mut wasm = Vec::::new(); file.read_to_end(&mut wasm) @@ -398,13 +401,25 @@ fn load_wasm_from_disk(dir: impl Into, checksum: &Checksum) -> VmResult /// code is required. So a non-existent file leads to an error as it /// indicates a bug. fn remove_wasm_from_disk(dir: impl Into, checksum: &Checksum) -> VmResult<()> { + // the files previously had no extension, so to allow for a smooth transition, we delete both let path = dir.into().join(checksum.to_hex()); + let wasm_path = path.with_extension("wasm"); - if !path.exists() { + let path_exists = path.exists(); + let wasm_path_exists = wasm_path.exists(); + if !path_exists && !wasm_path_exists { return Err(VmError::cache_err("Wasm file does not exist")); } - fs::remove_file(path).map_err(|_e| VmError::cache_err("Error removing Wasm file from disk"))?; + if path_exists { + fs::remove_file(path) + .map_err(|_e| VmError::cache_err("Error removing Wasm file from disk"))?; + } + + if wasm_path_exists { + fs::remove_file(wasm_path) + .map_err(|_e| VmError::cache_err("Error removing Wasm file from disk"))?; + } Ok(()) } @@ -606,7 +621,8 @@ mod tests { .path() .join(STATE_DIR) .join(WASM_DIR) - .join(checksum.to_hex()); + .join(checksum.to_hex()) + .with_extension("wasm"); let mut file = OpenOptions::new().write(true).open(filepath).unwrap(); file.write_all(b"broken data").unwrap(); @@ -1169,4 +1185,31 @@ mod tests { let non_id = Checksum::generate(b"non_existent"); cache.unpin(&non_id).unwrap(); } + + #[test] + fn loading_without_extension_works() { + let tmp_dir = TempDir::new().unwrap(); + let options = CacheOptions { + base_dir: tmp_dir.path().to_path_buf(), + available_capabilities: default_capabilities(), + memory_cache_size: TESTING_MEMORY_CACHE_SIZE, + instance_memory_limit: TESTING_MEMORY_LIMIT, + }; + let cache: Cache = + unsafe { Cache::new(options).unwrap() }; + let checksum = cache.save_wasm(CONTRACT).unwrap(); + + // Move the saved wasm to the old path (without extension) + let old_path = tmp_dir + .path() + .join(STATE_DIR) + .join(WASM_DIR) + .join(checksum.to_hex()); + let new_path = old_path.with_extension("wasm"); + fs::rename(new_path, old_path).unwrap(); + + // loading wasm from before the wasm extension was added should still work + let restored = cache.load_wasm(&checksum).unwrap(); + assert_eq!(restored, CONTRACT); + } } diff --git a/packages/vm/src/compatibility.rs b/packages/vm/src/compatibility.rs index c2c9826a9d..428d51554f 100644 --- a/packages/vm/src/compatibility.rs +++ b/packages/vm/src/compatibility.rs @@ -1,4 +1,4 @@ -use parity_wasm::elements::{External, ImportEntry, Module}; +use parity_wasm::elements::{External, ImportEntry, Module, TableType}; use std::collections::BTreeSet; use std::collections::HashSet; @@ -49,10 +49,31 @@ const SUPPORTED_INTERFACE_VERSIONS: &[&str] = &[ ]; const MEMORY_LIMIT: u32 = 512; // in pages +/// The upper limit for the `max` value of each table. CosmWasm contracts have +/// initial=max for 1 table. See +/// +/// ```plain +/// $ wasm-objdump --section=table -x packages/vm/testdata/hackatom.wasm +/// Section Details: +/// +/// Table[1]: +/// - table[0] type=funcref initial=161 max=161 +/// ``` +/// +/// As of March 2023, on Juno mainnet the largest value for production contracts +/// is 485. Most are between 100 and 300. +const TABLE_SIZE_LIMIT: u32 = 2500; // entries + +/// If the contract has more than this amount of imports, it will be rejected +/// during static validation before even looking into the imports. We keep this +/// number high since failing early gives less detailed error messages. Especially +/// when a user accidentally includes wasm-bindgen, they get a bunch of unsupported imports. +const MAX_IMPORTS: usize = 100; /// Checks if the data is valid wasm and compatibility with the CosmWasm API (imports and exports) pub fn check_wasm(wasm_code: &[u8], available_capabilities: &HashSet) -> VmResult<()> { let module = deserialize_wasm(wasm_code)?; + check_wasm_tables(&module)?; check_wasm_memories(&module)?; check_interface_version(&module)?; check_wasm_exports(&module)?; @@ -61,6 +82,38 @@ pub fn check_wasm(wasm_code: &[u8], available_capabilities: &HashSet) -> Ok(()) } +fn check_wasm_tables(module: &Module) -> VmResult<()> { + let sections: &[TableType] = module + .table_section() + .map_or(&[], |section| section.entries()); + match sections.len() { + 0 => Ok(()), + 1 => { + let limits = sections[0].limits(); + if let Some(maximum) = limits.maximum() { + if limits.initial() > maximum { + return Err(VmError::static_validation_err( + "Wasm contract's first table section has a initial limit > max limit", + )); + } + if maximum > TABLE_SIZE_LIMIT { + return Err(VmError::static_validation_err( + "Wasm contract's first table section has a too large max limit", + )); + } + Ok(()) + } else { + Err(VmError::static_validation_err( + "Wasm contract must not have unbound table section", + )) + } + } + _ => Err(VmError::static_validation_err( + "Wasm contract must not have more than 1 table section", + )), + } +} + fn check_wasm_memories(module: &Module) -> VmResult<()> { let section = match module.memory_section() { Some(section) => section, @@ -144,15 +197,23 @@ fn check_wasm_exports(module: &Module) -> VmResult<()> { /// When this is not the case, we either have an incompatibility between contract and VM /// or a error in the contract. fn check_wasm_imports(module: &Module, supported_imports: &[&str]) -> VmResult<()> { - let required_imports: Vec = module + let required_imports: &[ImportEntry] = module .import_section() - .map_or(vec![], |import_section| import_section.entries().to_vec()); - let required_import_names: BTreeSet<_> = - required_imports.iter().map(full_import_name).collect(); + .map_or(&[], |import_section| import_section.entries()); + + if required_imports.len() > MAX_IMPORTS { + return Err(VmError::static_validation_err(format!( + "Import count exceeds limit. Imports: {}. Limit: {}.", + required_imports.len(), + MAX_IMPORTS + ))); + } for required_import in required_imports { - let full_name = full_import_name(&required_import); + let full_name = full_import_name(required_import); if !supported_imports.contains(&full_name.as_str()) { + let required_import_names: BTreeSet<_> = + required_imports.iter().map(full_import_name).collect(); return Err(VmError::static_validation_err(format!( "Wasm contract requires unsupported import: \"{}\". Required imports: {}. Available imports: {:?}.", full_name, required_import_names.to_string_limited(200), supported_imports @@ -252,6 +313,38 @@ mod tests { }; } + #[test] + fn check_wasm_tables_works() { + // No tables is fine + let wasm = wat::parse_str("(module)").unwrap(); + check_wasm_tables(&deserialize_wasm(&wasm).unwrap()).unwrap(); + + // One table (bound) + let wasm = wat::parse_str("(module (table $name 123 123 funcref))").unwrap(); + check_wasm_tables(&deserialize_wasm(&wasm).unwrap()).unwrap(); + + // One table (bound, initial > max) + let wasm = wat::parse_str("(module (table $name 124 123 funcref))").unwrap(); + let err = check_wasm_tables(&deserialize_wasm(&wasm).unwrap()).unwrap_err(); + assert!(err + .to_string() + .contains("Wasm contract's first table section has a initial limit > max limit")); + + // One table (bound, max too large) + let wasm = wat::parse_str("(module (table $name 100 9999 funcref))").unwrap(); + let err = check_wasm_tables(&deserialize_wasm(&wasm).unwrap()).unwrap_err(); + assert!(err + .to_string() + .contains("Wasm contract's first table section has a too large max limit")); + + // One table (unbound) + let wasm = wat::parse_str("(module (table $name 100 funcref))").unwrap(); + let err = check_wasm_tables(&deserialize_wasm(&wasm).unwrap()).unwrap_err(); + assert!(err + .to_string() + .contains("Wasm contract must not have unbound table section")); + } + #[test] fn check_wasm_memories_ok() { let wasm = wat::parse_str("(module (memory 1))").unwrap(); @@ -561,6 +654,124 @@ mod tests { check_wasm_imports(&deserialize_wasm(&wasm).unwrap(), SUPPORTED_IMPORTS).unwrap(); } + #[test] + fn check_wasm_imports_exceeds_limit() { + let wasm = wat::parse_str( + r#"(module + (import "env" "db_write" (func (param i32 i32) (result i32))) + (import "env" "db_remove" (func (param i32) (result i32))) + (import "env" "addr_validate" (func (param i32) (result i32))) + (import "env" "addr_canonicalize" (func (param i32 i32) (result i32))) + (import "env" "addr_humanize" (func (param i32 i32) (result i32))) + (import "env" "secp256k1_verify" (func (param i32 i32 i32) (result i32))) + (import "env" "secp256k1_recover_pubkey" (func (param i32 i32 i32) (result i64))) + (import "env" "ed25519_verify" (func (param i32 i32 i32) (result i32))) + (import "env" "ed25519_batch_verify" (func (param i32 i32 i32) (result i32))) + (import "env" "spam01" (func (param i32 i32) (result i32))) + (import "env" "spam02" (func (param i32 i32) (result i32))) + (import "env" "spam03" (func (param i32 i32) (result i32))) + (import "env" "spam04" (func (param i32 i32) (result i32))) + (import "env" "spam05" (func (param i32 i32) (result i32))) + (import "env" "spam06" (func (param i32 i32) (result i32))) + (import "env" "spam07" (func (param i32 i32) (result i32))) + (import "env" "spam08" (func (param i32 i32) (result i32))) + (import "env" "spam09" (func (param i32 i32) (result i32))) + (import "env" "spam10" (func (param i32 i32) (result i32))) + (import "env" "spam11" (func (param i32 i32) (result i32))) + (import "env" "spam12" (func (param i32 i32) (result i32))) + (import "env" "spam13" (func (param i32 i32) (result i32))) + (import "env" "spam14" (func (param i32 i32) (result i32))) + (import "env" "spam15" (func (param i32 i32) (result i32))) + (import "env" "spam16" (func (param i32 i32) (result i32))) + (import "env" "spam17" (func (param i32 i32) (result i32))) + (import "env" "spam18" (func (param i32 i32) (result i32))) + (import "env" "spam19" (func (param i32 i32) (result i32))) + (import "env" "spam20" (func (param i32 i32) (result i32))) + (import "env" "spam21" (func (param i32 i32) (result i32))) + (import "env" "spam22" (func (param i32 i32) (result i32))) + (import "env" "spam23" (func (param i32 i32) (result i32))) + (import "env" "spam24" (func (param i32 i32) (result i32))) + (import "env" "spam25" (func (param i32 i32) (result i32))) + (import "env" "spam26" (func (param i32 i32) (result i32))) + (import "env" "spam27" (func (param i32 i32) (result i32))) + (import "env" "spam28" (func (param i32 i32) (result i32))) + (import "env" "spam29" (func (param i32 i32) (result i32))) + (import "env" "spam30" (func (param i32 i32) (result i32))) + (import "env" "spam31" (func (param i32 i32) (result i32))) + (import "env" "spam32" (func (param i32 i32) (result i32))) + (import "env" "spam33" (func (param i32 i32) (result i32))) + (import "env" "spam34" (func (param i32 i32) (result i32))) + (import "env" "spam35" (func (param i32 i32) (result i32))) + (import "env" "spam36" (func (param i32 i32) (result i32))) + (import "env" "spam37" (func (param i32 i32) (result i32))) + (import "env" "spam38" (func (param i32 i32) (result i32))) + (import "env" "spam39" (func (param i32 i32) (result i32))) + (import "env" "spam40" (func (param i32 i32) (result i32))) + (import "env" "spam41" (func (param i32 i32) (result i32))) + (import "env" "spam42" (func (param i32 i32) (result i32))) + (import "env" "spam43" (func (param i32 i32) (result i32))) + (import "env" "spam44" (func (param i32 i32) (result i32))) + (import "env" "spam45" (func (param i32 i32) (result i32))) + (import "env" "spam46" (func (param i32 i32) (result i32))) + (import "env" "spam47" (func (param i32 i32) (result i32))) + (import "env" "spam48" (func (param i32 i32) (result i32))) + (import "env" "spam49" (func (param i32 i32) (result i32))) + (import "env" "spam50" (func (param i32 i32) (result i32))) + (import "env" "spam51" (func (param i32 i32) (result i32))) + (import "env" "spam52" (func (param i32 i32) (result i32))) + (import "env" "spam53" (func (param i32 i32) (result i32))) + (import "env" "spam54" (func (param i32 i32) (result i32))) + (import "env" "spam55" (func (param i32 i32) (result i32))) + (import "env" "spam56" (func (param i32 i32) (result i32))) + (import "env" "spam57" (func (param i32 i32) (result i32))) + (import "env" "spam58" (func (param i32 i32) (result i32))) + (import "env" "spam59" (func (param i32 i32) (result i32))) + (import "env" "spam60" (func (param i32 i32) (result i32))) + (import "env" "spam61" (func (param i32 i32) (result i32))) + (import "env" "spam62" (func (param i32 i32) (result i32))) + (import "env" "spam63" (func (param i32 i32) (result i32))) + (import "env" "spam64" (func (param i32 i32) (result i32))) + (import "env" "spam65" (func (param i32 i32) (result i32))) + (import "env" "spam66" (func (param i32 i32) (result i32))) + (import "env" "spam67" (func (param i32 i32) (result i32))) + (import "env" "spam68" (func (param i32 i32) (result i32))) + (import "env" "spam69" (func (param i32 i32) (result i32))) + (import "env" "spam70" (func (param i32 i32) (result i32))) + (import "env" "spam71" (func (param i32 i32) (result i32))) + (import "env" "spam72" (func (param i32 i32) (result i32))) + (import "env" "spam73" (func (param i32 i32) (result i32))) + (import "env" "spam74" (func (param i32 i32) (result i32))) + (import "env" "spam75" (func (param i32 i32) (result i32))) + (import "env" "spam76" (func (param i32 i32) (result i32))) + (import "env" "spam77" (func (param i32 i32) (result i32))) + (import "env" "spam78" (func (param i32 i32) (result i32))) + (import "env" "spam79" (func (param i32 i32) (result i32))) + (import "env" "spam80" (func (param i32 i32) (result i32))) + (import "env" "spam81" (func (param i32 i32) (result i32))) + (import "env" "spam82" (func (param i32 i32) (result i32))) + (import "env" "spam83" (func (param i32 i32) (result i32))) + (import "env" "spam84" (func (param i32 i32) (result i32))) + (import "env" "spam85" (func (param i32 i32) (result i32))) + (import "env" "spam86" (func (param i32 i32) (result i32))) + (import "env" "spam87" (func (param i32 i32) (result i32))) + (import "env" "spam88" (func (param i32 i32) (result i32))) + (import "env" "spam89" (func (param i32 i32) (result i32))) + (import "env" "spam90" (func (param i32 i32) (result i32))) + (import "env" "spam91" (func (param i32 i32) (result i32))) + (import "env" "spam92" (func (param i32 i32) (result i32))) + )"#, + ) + .unwrap(); + let err = + check_wasm_imports(&deserialize_wasm(&wasm).unwrap(), SUPPORTED_IMPORTS).unwrap_err(); + match err { + VmError::StaticValidationErr { msg, .. } => { + assert_eq!(msg, "Import count exceeds limit. Imports: 101. Limit: 100."); + } + err => panic!("Unexpected error: {:?}", err), + } + } + #[test] fn check_wasm_imports_missing() { let wasm = wat::parse_str( diff --git a/packages/vm/src/modules/file_system_cache.rs b/packages/vm/src/modules/file_system_cache.rs index e5041a9900..a78b5661a3 100644 --- a/packages/vm/src/modules/file_system_cache.rs +++ b/packages/vm/src/modules/file_system_cache.rs @@ -1,9 +1,10 @@ use std::fs; +use std::hash::Hash; use std::io; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use thiserror::Error; -use wasmer::{DeserializeError, Module, Store}; +use wasmer::{DeserializeError, Module, Store, Target}; use crate::checksum::Checksum; use crate::errors::{VmError, VmResult}; @@ -43,14 +44,13 @@ use crate::modules::current_wasmer_module_version; /// A change in memory layout of some types in Rust [std] caused /// [issues with module deserialization](https://github.com/CosmWasm/wasmvm/issues/426). /// To work around this, the version was bumped to "v5" here to invalidate these corrupt caches. -const MODULE_SERIALIZATION_VERSION: &str = "v5"; +/// - **v6**:
+/// Version for cosmwasm_vm 1.3+ which adds a sub-folder with the target identier for the modules. +const MODULE_SERIALIZATION_VERSION: &str = "v6"; /// Representation of a directory that contains compiled Wasm artifacts. pub struct FileSystemCache { - /// The base path this cache operates in. Within this path, versioned directories are created. - /// A sophisticated version of this cache might be able to read multiple input versions in the future. - base_path: PathBuf, - wasmer_module_version: u32, + modules_path: PathBuf, } /// An error type that hides system specific error information @@ -75,41 +75,37 @@ impl FileSystemCache { /// /// This method is unsafe because there's no way to ensure the artifacts /// stored in this cache haven't been corrupted or tampered with. - pub unsafe fn new(path: impl Into) -> Result { - let wasmer_module_version = current_wasmer_module_version(); - - let path: PathBuf = path.into(); - if path.exists() { - let metadata = path + pub unsafe fn new(base_path: impl Into) -> Result { + let base_path: PathBuf = base_path.into(); + if base_path.exists() { + let metadata = base_path .metadata() .map_err(|_e| NewFileSystemCacheError::CouldntGetMetadata)?; - if metadata.is_dir() { - if !metadata.permissions().readonly() { - Ok(Self { - base_path: path, - wasmer_module_version, - }) - } else { - Err(NewFileSystemCacheError::ReadonlyPath) - } - } else { - Err(NewFileSystemCacheError::ExistsButNoDirectory) + if !metadata.is_dir() { + return Err(NewFileSystemCacheError::ExistsButNoDirectory); + } + if metadata.permissions().readonly() { + return Err(NewFileSystemCacheError::ReadonlyPath); } } else { // Create the directory and any parent directories if they don't yet exist. - mkdir_p(&path).map_err(|_e| NewFileSystemCacheError::CouldntCreatePath)?; - Ok(Self { - base_path: path, - wasmer_module_version, - }) + mkdir_p(&base_path).map_err(|_e| NewFileSystemCacheError::CouldntCreatePath)?; } + + Ok(Self { + modules_path: modules_path( + &base_path, + current_wasmer_module_version(), + &Target::default(), + ), + }) } /// Loads a serialized module from the file system and returns a module (i.e. artifact + store), /// along with the size of the serialized module. pub fn load(&self, checksum: &Checksum, store: &Store) -> VmResult> { let filename = checksum.to_hex(); - let file_path = self.latest_modules_path().join(filename); + let file_path = self.modules_path.join(filename); let result = unsafe { Module::deserialize_from_file(store, file_path) }; match result { @@ -130,12 +126,11 @@ impl FileSystemCache { /// Stores a serialized module to the file system. Returns the size of the serialized module. pub fn store(&mut self, checksum: &Checksum, module: &Module) -> VmResult<()> { - let modules_dir = self.latest_modules_path(); - mkdir_p(&modules_dir) + mkdir_p(&self.modules_path) .map_err(|_e| VmError::cache_err("Error creating modules directory"))?; let filename = checksum.to_hex(); - let path = modules_dir.join(filename); + let path = self.modules_path.join(filename); module .serialize_to_file(path) .map_err(|e| VmError::cache_err(format!("Error writing module to disk: {}", e)))?; @@ -147,7 +142,7 @@ impl FileSystemCache { /// Returns true if the file existed and false if the file did not exist. pub fn remove(&mut self, checksum: &Checksum) -> VmResult { let filename = checksum.to_hex(); - let file_path = self.latest_modules_path().join(filename); + let file_path = self.modules_path.join(filename); if file_path.exists() { fs::remove_file(file_path) @@ -157,15 +152,27 @@ impl FileSystemCache { Ok(false) } } +} - /// The path to the latest version of the modules. - fn latest_modules_path(&self) -> PathBuf { - let version = format!( - "{}-wasmer{}", - MODULE_SERIALIZATION_VERSION, self.wasmer_module_version - ); - self.base_path.join(version) - } +/// Creates an identifier for the Wasmer `Target` that is used for +/// cache invalidation. The output is reasonable human friendly to be useable +/// in file path component. +fn target_id(target: &Target) -> String { + // Use a custom Hasher implementation to avoid randomization. + let mut deterministic_hasher = crc32fast::Hasher::new(); + target.hash(&mut deterministic_hasher); + let hash = deterministic_hasher.finalize(); + format!("{}-{:08X}", target.triple(), hash) // print 4 byte hash as 8 hex characters +} + +/// The path to the latest version of the modules. +fn modules_path(base_path: &Path, wasmer_module_version: u32, target: &Target) -> PathBuf { + let version_dir = format!( + "{}-wasmer{}", + MODULE_SERIALIZATION_VERSION, wasmer_module_version + ); + let target_dir = target_id(target); + base_path.join(version_dir).join(target_dir) } #[cfg(test)] @@ -239,11 +246,13 @@ mod tests { let module = compile(&wasm, None, &[]).unwrap(); cache.store(&checksum, &module).unwrap(); - let file_path = format!( - "{}/v5-wasmer1/{}", + let mut globber = glob::glob(&format!( + "{}/v6-wasmer1/**/{}", tmp_dir.path().to_string_lossy(), checksum - ); + )) + .expect("Failed to read glob pattern"); + let file_path = globber.next().unwrap().unwrap(); let _serialized_module = fs::read(file_path).unwrap(); } @@ -276,4 +285,50 @@ mod tests { let existed = cache.remove(&checksum).unwrap(); assert!(!existed); } + + #[test] + fn target_id_works() { + let triple = wasmer::Triple { + architecture: wasmer::Architecture::X86_64, + vendor: target_lexicon::Vendor::Nintendo, + operating_system: target_lexicon::OperatingSystem::Fuchsia, + environment: target_lexicon::Environment::Gnu, + binary_format: target_lexicon::BinaryFormat::Coff, + }; + let target = Target::new(triple.clone(), wasmer::CpuFeature::POPCNT.into()); + let id = target_id(&target); + assert_eq!(id, "x86_64-nintendo-fuchsia-gnu-coff-4721E3F4"); + // Changing CPU features changes the hash part + let target = Target::new(triple, wasmer::CpuFeature::AVX512DQ.into()); + let id = target_id(&target); + assert_eq!(id, "x86_64-nintendo-fuchsia-gnu-coff-D5C8034F"); + + // Works for durrect target (hashing is deterministic); + let target = Target::default(); + let id1 = target_id(&target); + let id2 = target_id(&target); + assert_eq!(id1, id2); + } + + #[test] + fn modules_path_works() { + let base = PathBuf::from("modules"); + let triple = wasmer::Triple { + architecture: wasmer::Architecture::X86_64, + vendor: target_lexicon::Vendor::Nintendo, + operating_system: target_lexicon::OperatingSystem::Fuchsia, + environment: target_lexicon::Environment::Gnu, + binary_format: target_lexicon::BinaryFormat::Coff, + }; + let target = Target::new(triple, wasmer::CpuFeature::POPCNT.into()); + let p = modules_path(&base, 17, &target); + assert_eq!( + p.as_os_str(), + if cfg!(windows) { + "modules\\v6-wasmer17\\x86_64-nintendo-fuchsia-gnu-coff-4721E3F4" + } else { + "modules/v6-wasmer17/x86_64-nintendo-fuchsia-gnu-coff-4721E3F4" + } + ); + } }