Skip to content

Create Envs, a factory for making mock Envs #2442

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
247 changes: 234 additions & 13 deletions packages/std/src/testing/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -392,17 +392,149 @@
/// // `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),
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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a more specific name would be better? Envs sounds very generic and doesn't really indicate that this is for testing only.

Copy link
Member Author

@webmaster128 webmaster128 Apr 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy for suggestions. But I doubt this will be used in the majority of test code.

You have to import it through cosmwasm_std::testing::Envs, so testing is implied here

chain_id: String,
contract_address: Addr,
/// The number of nanoseconds between two consecutive blocks
block_time: u64,
last_height: u64,
last_time: Timestamp,
envs_produced: u64,
}

#[derive(Clone)]
pub struct EnvsOptions {
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
pub initial_height: u64,
// The block time before the first `make` call
pub initial_time: Timestamp,
pub 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(),
},
transaction: Some(TransactionInfo { index: 3 }),
contract: ContractInfo {
address: contract_addr,
},
}
}
}

impl Envs {
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: options.block_time,
last_height: options.initial_height,
last_time: options.initial_time,
envs_produced: 0,
}
}

pub fn make(&mut self) -> Env {
self.checked_make().unwrap()
}

fn checked_make(&mut self) -> Option<Env> {
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; // does not overflow because height increment fails first

Some(Env {
block: BlockInfo {
height,
time,
chain_id: self.chain_id.clone(),
},
transaction: Some(TransactionInfo { index: 3 }),
contract: ContractInfo {
address: self.contract_address.clone(),
},
})
}
}

impl Default for Envs {
fn default() -> Self {
Envs::with_options(EnvsOptions::default())
}

Check warning on line 528 in packages/std/src/testing/mock.rs

View check run for this annotation

Codecov / codecov/patch

packages/std/src/testing/mock.rs#L526-L528

Added lines #L526 - L528 were not covered by tests
}

// 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<Self::Item> {
self.checked_make()
}
}

Expand Down Expand Up @@ -1267,9 +1399,98 @@
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]
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_iterator() {
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)
);

// 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]
Expand Down
4 changes: 2 additions & 2 deletions packages/std/src/testing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, MockApi, MockQuerier, MockQuerierCustomHandlerResult,
MOCK_CONTRACT_ADDR,
mock_wasmd_attr, BankQuerier, Envs, EnvsOptions, MockApi, MockQuerier,
MockQuerierCustomHandlerResult, MOCK_CONTRACT_ADDR,
};
#[cfg(feature = "stargate")]
pub use mock::{
Expand Down
Loading