From cf18d0c1c48d1dea672b732d2067cc2a6b557d0b Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Mon, 7 Apr 2025 14:24:33 +0200 Subject: [PATCH 1/5] Test all values of mock_env --- packages/std/src/testing/mock.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/std/src/testing/mock.rs b/packages/std/src/testing/mock.rs index a3a6a1450..3b7d1231d 100644 --- a/packages/std/src/testing/mock.rs +++ b/packages/std/src/testing/mock.rs @@ -1258,9 +1258,22 @@ mod tests { include_bytes!("../../../crypto/testdata/eth-headers/1699693797.394876721s.json"); #[test] - fn mock_env_matches_mock_contract_addr() { - let contract_address = mock_env().contract.address; - assert_eq!(contract_address, Addr::unchecked(MOCK_CONTRACT_ADDR)); + fn mock_env_works() { + let env = mock_env(); + assert_eq!( + env, + Env { + block: BlockInfo { + height: 12345, + time: Timestamp::from_nanos(1571797419879305533), + chain_id: "cosmos-testnet-14002".to_string() + }, + transaction: Some(TransactionInfo { index: 3 }), + contract: ContractInfo { + address: Addr::unchecked(MOCK_CONTRACT_ADDR) + } + } + ) } #[test] From 4c5f9f8fa72294579aba8a09376f86895ba5a11d Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 26 Mar 2025 16:08:49 +0100 Subject: [PATCH 2/5] Create `Envs`, a factory for making mock `Env`s --- packages/std/src/testing/mock.rs | 180 +++++++++++++++++++++++++++++-- packages/std/src/testing/mod.rs | 2 +- 2 files changed, 170 insertions(+), 12 deletions(-) diff --git a/packages/std/src/testing/mock.rs b/packages/std/src/testing/mock.rs index 3b7d1231d..a56185759 100644 --- a/packages/std/src/testing/mock.rs +++ b/packages/std/src/testing/mock.rs @@ -390,17 +390,110 @@ fn validate_length(bytes: &[u8]) -> StdResult<()> { /// // `env3` is one block and 5.5 seconds later /// ``` pub fn mock_env() -> Env { - let contract_addr = MockApi::default().addr_make("cosmos2contract"); - Env { - block: BlockInfo { - height: 12_345, - time: Timestamp::from_nanos(1_571_797_419_879_305_533), - chain_id: "cosmos-testnet-14002".to_string(), - }, - transaction: Some(TransactionInfo { index: 3 }), - contract: ContractInfo { - address: contract_addr, - }, + let mut envs = Envs::new(BECH32_PREFIX); + envs.make() +} + +/// A factory type that stores chain information such as bech32 prefix and can make mock `Env`s from there. +/// +/// It increments height for each mock call and block time by 5 seconds but is otherwise dumb. +/// +/// In contrast to using `mock_env`, the bech32 prefix must always be specified. +/// +/// ## Examples +/// +/// Typical usage +/// +/// ``` +/// # use cosmwasm_std::Timestamp; +/// use cosmwasm_std::testing::Envs; +/// +/// let mut envs = Envs::new("food"); +/// +/// let env = envs.make(); +/// assert_eq!(env.contract.address.as_str(), "food1jpev2csrppg792t22rn8z8uew8h3sjcpglcd0qv9g8gj8ky922ts74yrjj"); +/// assert_eq!(env.block.height, 12_345); +/// assert_eq!(env.block.time, Timestamp::from_nanos(1_571_797_419_879_305_533)); +/// +/// let env = envs.make(); +/// assert_eq!(env.contract.address.as_str(), "food1jpev2csrppg792t22rn8z8uew8h3sjcpglcd0qv9g8gj8ky922ts74yrjj"); +/// assert_eq!(env.block.height, 12_346); +/// assert_eq!(env.block.time, Timestamp::from_nanos(1_571_797_424_879_305_533)); +/// +/// let env = envs.make(); +/// assert_eq!(env.contract.address.as_str(), "food1jpev2csrppg792t22rn8z8uew8h3sjcpglcd0qv9g8gj8ky922ts74yrjj"); +/// assert_eq!(env.block.height, 12_347); +/// assert_eq!(env.block.time, Timestamp::from_nanos(1_571_797_429_879_305_533)); +/// ``` +/// +/// Or use with iterator +/// +/// ``` +/// # use cosmwasm_std::Timestamp; +/// use cosmwasm_std::testing::Envs; +/// +/// let mut envs = Envs::new("food"); +/// +/// for (index, env) in envs.take(100).enumerate() { +/// assert_eq!(env.contract.address.as_str(), "food1jpev2csrppg792t22rn8z8uew8h3sjcpglcd0qv9g8gj8ky922ts74yrjj"); +/// assert_eq!(env.block.height, 12_345 + index as u64); +/// assert_eq!(env.block.time, Timestamp::from_nanos(1_571_797_419_879_305_533).plus_seconds((index*5) as u64)); +/// } +/// ``` +pub struct Envs { + contract_address: Addr, + last_height: u64, + last_time: Timestamp, + envs_produced: u64, +} + +impl Envs { + pub fn new( + bech32_prefix: &'static str, /* static due to MockApi's Copy requirement. No better idea for now. */ + ) -> Self { + let api = MockApi::default().with_prefix(bech32_prefix); + Envs { + // Default values here for compatibility with old `mock_env` function. They could be changed to anything else if there is a good reason. + contract_address: api.addr_make("cosmos2contract"), + last_height: 12_344, + last_time: Timestamp::from_nanos(1_571_797_419_879_305_533).minus_seconds(5), + envs_produced: 0, + } + } + + pub fn make(&mut self) -> Env { + let height = self.last_height + 1; + let time = self.last_time.plus_seconds(5); + + self.last_height = height; + self.last_time = time; + self.envs_produced += 1; + + Env { + block: BlockInfo { + height, + time, + chain_id: "cosmos-testnet-14002".to_string(), + }, + transaction: Some(TransactionInfo { index: 3 }), + contract: ContractInfo { + address: self.contract_address.clone(), + }, + } + } +} + +// The iterator implementation can produce 1 million envs and then stops for no good reason. +impl Iterator for Envs { + type Item = Env; + + fn next(&mut self) -> Option { + if self.envs_produced < 1_000_000 { + let item = self.make(); + Some(item) + } else { + None + } } } @@ -1276,6 +1369,71 @@ mod tests { ) } + #[test] + fn envs_works() { + let mut envs = Envs::new("food"); + + let env = envs.make(); + assert_eq!( + env.contract.address.as_str(), + "food1jpev2csrppg792t22rn8z8uew8h3sjcpglcd0qv9g8gj8ky922ts74yrjj" + ); + assert_eq!(env.block.height, 12_345); + assert_eq!( + env.block.time, + Timestamp::from_nanos(1_571_797_419_879_305_533) + ); + + let env = envs.make(); + assert_eq!( + env.contract.address.as_str(), + "food1jpev2csrppg792t22rn8z8uew8h3sjcpglcd0qv9g8gj8ky922ts74yrjj" + ); + assert_eq!(env.block.height, 12_346); + assert_eq!( + env.block.time, + Timestamp::from_nanos(1_571_797_424_879_305_533) + ); + + let env = envs.make(); + assert_eq!( + env.contract.address.as_str(), + "food1jpev2csrppg792t22rn8z8uew8h3sjcpglcd0qv9g8gj8ky922ts74yrjj" + ); + assert_eq!(env.block.height, 12_347); + assert_eq!( + env.block.time, + Timestamp::from_nanos(1_571_797_429_879_305_533) + ); + } + + #[test] + fn envs_implements_iteratorworks() { + let envs = Envs::new("food"); + + let result: Vec<_> = envs.into_iter().take(5).collect(); + + assert_eq!( + result[0].contract.address.as_str(), + "food1jpev2csrppg792t22rn8z8uew8h3sjcpglcd0qv9g8gj8ky922ts74yrjj" + ); + assert_eq!(result[0].block.height, 12_345); + assert_eq!( + result[0].block.time, + Timestamp::from_nanos(1_571_797_419_879_305_533) + ); + + assert_eq!( + result[4].contract.address.as_str(), + "food1jpev2csrppg792t22rn8z8uew8h3sjcpglcd0qv9g8gj8ky922ts74yrjj" + ); + assert_eq!(result[4].block.height, 12_349); + assert_eq!( + result[4].block.time, + Timestamp::from_nanos(1_571_797_439_879_305_533) + ); + } + #[test] fn addr_validate_works() { // default prefix is 'cosmwasm' diff --git a/packages/std/src/testing/mod.rs b/packages/std/src/testing/mod.rs index ef4df1326..60bc6cfc8 100644 --- a/packages/std/src/testing/mod.rs +++ b/packages/std/src/testing/mod.rs @@ -21,7 +21,7 @@ pub use mock::DistributionQuerier; pub use mock::StakingQuerier; pub use mock::{ mock_dependencies, mock_dependencies_with_balance, mock_dependencies_with_balances, mock_env, - mock_wasmd_attr, BankQuerier, MockApi, MockQuerier, MockQuerierCustomHandlerResult, + mock_wasmd_attr, BankQuerier, Envs, MockApi, MockQuerier, MockQuerierCustomHandlerResult, MOCK_CONTRACT_ADDR, }; #[cfg(feature = "stargate")] From b30694d3020200827f564f6e6e718f317fbaed34 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Mon, 7 Apr 2025 13:24:57 +0200 Subject: [PATCH 3/5] Make block_time variable and iterate until overflow --- packages/std/src/testing/mock.rs | 40 +++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/packages/std/src/testing/mock.rs b/packages/std/src/testing/mock.rs index a56185759..a708cab9f 100644 --- a/packages/std/src/testing/mock.rs +++ b/packages/std/src/testing/mock.rs @@ -442,6 +442,8 @@ pub fn mock_env() -> Env { /// ``` pub struct Envs { contract_address: Addr, + /// The number of nanoseconds between two consecutive blocks + block_time: u64, last_height: u64, last_time: Timestamp, envs_produced: u64, @@ -455,6 +457,7 @@ impl Envs { Envs { // Default values here for compatibility with old `mock_env` function. They could be changed to anything else if there is a good reason. contract_address: api.addr_make("cosmos2contract"), + block_time: 5_000_000_000, // 5s last_height: 12_344, last_time: Timestamp::from_nanos(1_571_797_419_879_305_533).minus_seconds(5), envs_produced: 0, @@ -462,14 +465,18 @@ impl Envs { } pub fn make(&mut self) -> Env { - let height = self.last_height + 1; - let time = self.last_time.plus_seconds(5); + self.checked_make().unwrap() + } + + fn checked_make(&mut self) -> Option { + let height = self.last_height.checked_add(1)?; + let time = Timestamp::from_nanos(self.last_time.nanos().checked_add(self.block_time)?); self.last_height = height; self.last_time = time; - self.envs_produced += 1; + self.envs_produced += 1; // does not overflow because height increment fails first - Env { + Some(Env { block: BlockInfo { height, time, @@ -479,21 +486,17 @@ impl Envs { contract: ContractInfo { address: self.contract_address.clone(), }, - } + }) } } -// The iterator implementation can produce 1 million envs and then stops for no good reason. +// The iterator implementation ends in case of overflows to avoid panics. +// Using this is recommended for very long running test suites. impl Iterator for Envs { type Item = Env; fn next(&mut self) -> Option { - if self.envs_produced < 1_000_000 { - let item = self.make(); - Some(item) - } else { - None - } + self.checked_make() } } @@ -1408,7 +1411,7 @@ mod tests { } #[test] - fn envs_implements_iteratorworks() { + fn envs_implements_iterator() { let envs = Envs::new("food"); let result: Vec<_> = envs.into_iter().take(5).collect(); @@ -1432,6 +1435,17 @@ mod tests { result[4].block.time, Timestamp::from_nanos(1_571_797_439_879_305_533) ); + + // Get a millions envs through iterator + let mut envs = Envs::new("yo"); + let first = envs.next().unwrap(); + let last = envs.take(1_000_000).last().unwrap(); + assert_eq!(first.block.height, 12_345); + assert_eq!(last.block.height, 1_012_345); + assert_eq!( + last.block.time, + first.block.time.plus_seconds(1_000_000 * 5) + ); } #[test] From f2c6360e4d968c1fff15a77da8a3a70818950fe0 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Mon, 7 Apr 2025 14:21:00 +0200 Subject: [PATCH 4/5] Add EnvsOptions to make block_time, initial_height, initial_time, chain_id configurable --- packages/std/src/testing/mock.rs | 51 +++++++++++++++++++++++++++----- packages/std/src/testing/mod.rs | 4 +-- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/packages/std/src/testing/mock.rs b/packages/std/src/testing/mock.rs index a708cab9f..3b952372b 100644 --- a/packages/std/src/testing/mock.rs +++ b/packages/std/src/testing/mock.rs @@ -441,6 +441,7 @@ pub fn mock_env() -> Env { /// } /// ``` pub struct Envs { + chain_id: String, contract_address: Addr, /// The number of nanoseconds between two consecutive blocks block_time: u64, @@ -449,17 +450,45 @@ pub struct Envs { envs_produced: u64, } +pub struct EnvsOptions { + bech32_prefix: &'static str, /* static due to MockApi's Copy requirement. No better idea for now. */ + block_time: u64, + // The height before the first `make` call + initial_height: u64, + // The block time before the first `make` call + initial_time: Timestamp, + chain_id: String, +} + +impl Default for EnvsOptions { + fn default() -> Self { + EnvsOptions { + bech32_prefix: BECH32_PREFIX, + block_time: 5_000_000_000, // 5s + initial_height: 12_344, + initial_time: Timestamp::from_nanos(1_571_797_419_879_305_533).minus_seconds(5), + chain_id: "cosmos-testnet-14002".to_string(), + } + } +} + impl Envs { - pub fn new( - bech32_prefix: &'static str, /* static due to MockApi's Copy requirement. No better idea for now. */ - ) -> Self { - let api = MockApi::default().with_prefix(bech32_prefix); + pub fn new(bech32_prefix: &'static str) -> Self { + Self::with_options(EnvsOptions { + bech32_prefix, + ..Default::default() + }) + } + + pub fn with_options(options: EnvsOptions) -> Self { + let api = MockApi::default().with_prefix(options.bech32_prefix); Envs { + chain_id: options.chain_id, // Default values here for compatibility with old `mock_env` function. They could be changed to anything else if there is a good reason. contract_address: api.addr_make("cosmos2contract"), - block_time: 5_000_000_000, // 5s - last_height: 12_344, - last_time: Timestamp::from_nanos(1_571_797_419_879_305_533).minus_seconds(5), + block_time: options.block_time, + last_height: options.initial_height, + last_time: options.initial_time, envs_produced: 0, } } @@ -480,7 +509,7 @@ impl Envs { block: BlockInfo { height, time, - chain_id: "cosmos-testnet-14002".to_string(), + chain_id: self.chain_id.clone(), }, transaction: Some(TransactionInfo { index: 3 }), contract: ContractInfo { @@ -490,6 +519,12 @@ impl Envs { } } +impl Default for Envs { + fn default() -> Self { + Envs::with_options(EnvsOptions::default()) + } +} + // The iterator implementation ends in case of overflows to avoid panics. // Using this is recommended for very long running test suites. impl Iterator for Envs { diff --git a/packages/std/src/testing/mod.rs b/packages/std/src/testing/mod.rs index 60bc6cfc8..5b37c1348 100644 --- a/packages/std/src/testing/mod.rs +++ b/packages/std/src/testing/mod.rs @@ -21,8 +21,8 @@ pub use mock::DistributionQuerier; pub use mock::StakingQuerier; pub use mock::{ mock_dependencies, mock_dependencies_with_balance, mock_dependencies_with_balances, mock_env, - mock_wasmd_attr, BankQuerier, Envs, MockApi, MockQuerier, MockQuerierCustomHandlerResult, - MOCK_CONTRACT_ADDR, + mock_wasmd_attr, BankQuerier, Envs, EnvsOptions, MockApi, MockQuerier, + MockQuerierCustomHandlerResult, MOCK_CONTRACT_ADDR, }; #[cfg(feature = "stargate")] pub use mock::{ From 9b5caf0bd741261c1a42174f3b35f84d55bdda91 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Mon, 7 Apr 2025 14:30:36 +0200 Subject: [PATCH 5/5] Make EnvsOptions fields pub --- packages/std/src/testing/mock.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/std/src/testing/mock.rs b/packages/std/src/testing/mock.rs index 3b952372b..213e00887 100644 --- a/packages/std/src/testing/mock.rs +++ b/packages/std/src/testing/mock.rs @@ -450,14 +450,15 @@ pub struct Envs { envs_produced: u64, } +#[derive(Clone)] pub struct EnvsOptions { - bech32_prefix: &'static str, /* static due to MockApi's Copy requirement. No better idea for now. */ - block_time: u64, + pub bech32_prefix: &'static str, /* static due to MockApi's Copy requirement. No better idea for now. */ + pub block_time: u64, // The height before the first `make` call - initial_height: u64, + pub initial_height: u64, // The block time before the first `make` call - initial_time: Timestamp, - chain_id: String, + pub initial_time: Timestamp, + pub chain_id: String, } impl Default for EnvsOptions {