Skip to content

Commit fcbd69f

Browse files
authored
Merge pull request #2442 from CosmWasm/envs
Create `Envs`, a factory for making mock `Env`s
2 parents 63cc0b6 + e6c698f commit fcbd69f

File tree

3 files changed

+257
-15
lines changed

3 files changed

+257
-15
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ and this project adheres to
2424
- cosmwasm-std: Add `WriteAcknowledgement` to `Ibc2Msg` - ([#2425])
2525
- cosmwasm-vm: Add `ibc2_packet_timeout` entrypoint - ([#2454])
2626
- cosmwasm-std: Add `Ibc2PacketTimeoutMsg` message - ([#2454])
27+
- cosmwasm-std: Add `cosmwasm_std::testing::Envs`, which is an `Env` factory for
28+
testing environments. It auto-increments block heights and timestamps. It
29+
allows for advanced configurations such as custom address prefixes. ([#2442])
2730

2831
## Changed
2932

@@ -104,6 +107,7 @@ and this project adheres to
104107
[#2432]: https://github.com/CosmWasm/cosmwasm/pull/2432
105108
[#2433]: https://github.com/CosmWasm/cosmwasm/pull/2433
106109
[#2438]: https://github.com/CosmWasm/cosmwasm/pull/2438
110+
[#2442]: https://github.com/CosmWasm/cosmwasm/pull/2442
107111
[#2450]: https://github.com/CosmWasm/cosmwasm/pull/2450
108112
[#2454]: https://github.com/CosmWasm/cosmwasm/pull/2454
109113
[#2458]: https://github.com/CosmWasm/cosmwasm/pull/2458

packages/std/src/testing/mock.rs

Lines changed: 251 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -390,17 +390,166 @@ fn validate_length(bytes: &[u8]) -> StdResult<()> {
390390
/// // `env3` is one block and 5.5 seconds later
391391
/// ```
392392
pub fn mock_env() -> Env {
393-
let contract_addr = MockApi::default().addr_make("cosmos2contract");
394-
Env {
395-
block: BlockInfo {
396-
height: 12_345,
397-
time: Timestamp::from_nanos(1_571_797_419_879_305_533),
393+
let mut envs = Envs::new(BECH32_PREFIX);
394+
envs.make()
395+
}
396+
397+
/// A factory type that stores chain information such as bech32 prefix and can make mock `Env`s from there.
398+
///
399+
/// It increments height for each mock call and block time by 5 seconds but is otherwise dumb.
400+
///
401+
/// In contrast to using `mock_env`, the bech32 prefix must always be specified.
402+
///
403+
/// ## Examples
404+
///
405+
/// Typical usage
406+
///
407+
/// ```
408+
/// # use cosmwasm_std::Timestamp;
409+
/// use cosmwasm_std::testing::Envs;
410+
///
411+
/// let mut envs = Envs::new("food");
412+
///
413+
/// let env = envs.make();
414+
/// assert_eq!(env.contract.address.as_str(), "food1jpev2csrppg792t22rn8z8uew8h3sjcpglcd0qv9g8gj8ky922ts74yrjj");
415+
/// assert_eq!(env.block.height, 12_345);
416+
/// assert_eq!(env.block.time, Timestamp::from_nanos(1_571_797_419_879_305_533));
417+
///
418+
/// let env = envs.make();
419+
/// assert_eq!(env.contract.address.as_str(), "food1jpev2csrppg792t22rn8z8uew8h3sjcpglcd0qv9g8gj8ky922ts74yrjj");
420+
/// assert_eq!(env.block.height, 12_346);
421+
/// assert_eq!(env.block.time, Timestamp::from_nanos(1_571_797_424_879_305_533));
422+
///
423+
/// let env = envs.make();
424+
/// assert_eq!(env.contract.address.as_str(), "food1jpev2csrppg792t22rn8z8uew8h3sjcpglcd0qv9g8gj8ky922ts74yrjj");
425+
/// assert_eq!(env.block.height, 12_347);
426+
/// assert_eq!(env.block.time, Timestamp::from_nanos(1_571_797_429_879_305_533));
427+
/// ```
428+
///
429+
/// Or use with iterator
430+
///
431+
/// ```
432+
/// # use cosmwasm_std::Timestamp;
433+
/// use cosmwasm_std::testing::Envs;
434+
///
435+
/// let mut envs = Envs::new("food");
436+
///
437+
/// for (index, env) in envs.take(100).enumerate() {
438+
/// assert_eq!(env.contract.address.as_str(), "food1jpev2csrppg792t22rn8z8uew8h3sjcpglcd0qv9g8gj8ky922ts74yrjj");
439+
/// assert_eq!(env.block.height, 12_345 + index as u64);
440+
/// assert_eq!(env.block.time, Timestamp::from_nanos(1_571_797_419_879_305_533).plus_seconds((index*5) as u64));
441+
/// }
442+
/// ```
443+
pub struct Envs {
444+
chain_id: String,
445+
contract_address: Addr,
446+
/// The number of nanoseconds between two consecutive blocks
447+
block_time: u64,
448+
last_height: u64,
449+
last_time: Timestamp,
450+
}
451+
452+
/// Options to create an `Envs` instance.
453+
///
454+
/// ## Examples
455+
///
456+
/// Must be constructed with the help of `Default` since new options might be added later.
457+
///
458+
/// ```
459+
/// # use cosmwasm_std::Timestamp;
460+
/// use cosmwasm_std::testing::{Envs, EnvsOptions};
461+
///
462+
/// let mut options = EnvsOptions::default();
463+
/// options.chain_id = "megachain".to_string();
464+
/// options.bech32_prefix = "mega";
465+
/// let mut envs = Envs::with_options(options);
466+
///
467+
/// let env = envs.make();
468+
/// assert_eq!(env.block.chain_id, "megachain");
469+
/// assert_eq!(env.contract.address.as_str(), "mega1jpev2csrppg792t22rn8z8uew8h3sjcpglcd0qv9g8gj8ky922ts7vnj8h");
470+
/// ```
471+
#[derive(Clone, Debug)]
472+
#[non_exhaustive]
473+
pub struct EnvsOptions {
474+
pub bech32_prefix: &'static str, /* static due to MockApi's Copy requirement. No better idea for now. */
475+
pub block_time: u64,
476+
// The height before the first `make` call
477+
pub initial_height: u64,
478+
// The block time before the first `make` call
479+
pub initial_time: Timestamp,
480+
pub chain_id: String,
481+
}
482+
483+
impl Default for EnvsOptions {
484+
fn default() -> Self {
485+
EnvsOptions {
486+
bech32_prefix: BECH32_PREFIX,
487+
block_time: 5_000_000_000, // 5s
488+
initial_height: 12_344,
489+
initial_time: Timestamp::from_nanos(1_571_797_419_879_305_533).minus_seconds(5),
398490
chain_id: "cosmos-testnet-14002".to_string(),
399-
},
400-
transaction: Some(TransactionInfo { index: 3 }),
401-
contract: ContractInfo {
402-
address: contract_addr,
403-
},
491+
}
492+
}
493+
}
494+
495+
impl Envs {
496+
pub fn new(bech32_prefix: &'static str) -> Self {
497+
Self::with_options(EnvsOptions {
498+
bech32_prefix,
499+
..Default::default()
500+
})
501+
}
502+
503+
pub fn with_options(options: EnvsOptions) -> Self {
504+
let api = MockApi::default().with_prefix(options.bech32_prefix);
505+
Envs {
506+
chain_id: options.chain_id,
507+
// Default values here for compatibility with old `mock_env` function. They could be changed to anything else if there is a good reason.
508+
contract_address: api.addr_make("cosmos2contract"),
509+
block_time: options.block_time,
510+
last_height: options.initial_height,
511+
last_time: options.initial_time,
512+
}
513+
}
514+
515+
pub fn make(&mut self) -> Env {
516+
self.checked_make().unwrap()
517+
}
518+
519+
fn checked_make(&mut self) -> Option<Env> {
520+
let height = self.last_height.checked_add(1)?;
521+
let time = Timestamp::from_nanos(self.last_time.nanos().checked_add(self.block_time)?);
522+
523+
self.last_height = height;
524+
self.last_time = time;
525+
526+
Some(Env {
527+
block: BlockInfo {
528+
height,
529+
time,
530+
chain_id: self.chain_id.clone(),
531+
},
532+
transaction: Some(TransactionInfo { index: 3 }),
533+
contract: ContractInfo {
534+
address: self.contract_address.clone(),
535+
},
536+
})
537+
}
538+
}
539+
540+
impl Default for Envs {
541+
fn default() -> Self {
542+
Envs::with_options(EnvsOptions::default())
543+
}
544+
}
545+
546+
// The iterator implementation ends in case of overflows to avoid panics.
547+
// Using this is recommended for very long running test suites.
548+
impl Iterator for Envs {
549+
type Item = Env;
550+
551+
fn next(&mut self) -> Option<Self::Item> {
552+
self.checked_make()
404553
}
405554
}
406555

@@ -1277,9 +1426,98 @@ mod tests {
12771426
include_bytes!("../../../crypto/testdata/eth-headers/1699693797.394876721s.json");
12781427

12791428
#[test]
1280-
fn mock_env_matches_mock_contract_addr() {
1281-
let contract_address = mock_env().contract.address;
1282-
assert_eq!(contract_address, Addr::unchecked(MOCK_CONTRACT_ADDR));
1429+
fn mock_env_works() {
1430+
let env = mock_env();
1431+
assert_eq!(
1432+
env,
1433+
Env {
1434+
block: BlockInfo {
1435+
height: 12345,
1436+
time: Timestamp::from_nanos(1571797419879305533),
1437+
chain_id: "cosmos-testnet-14002".to_string()
1438+
},
1439+
transaction: Some(TransactionInfo { index: 3 }),
1440+
contract: ContractInfo {
1441+
address: Addr::unchecked(MOCK_CONTRACT_ADDR)
1442+
}
1443+
}
1444+
)
1445+
}
1446+
1447+
#[test]
1448+
fn envs_works() {
1449+
let mut envs = Envs::new("food");
1450+
1451+
let env = envs.make();
1452+
assert_eq!(
1453+
env.contract.address.as_str(),
1454+
"food1jpev2csrppg792t22rn8z8uew8h3sjcpglcd0qv9g8gj8ky922ts74yrjj"
1455+
);
1456+
assert_eq!(env.block.height, 12_345);
1457+
assert_eq!(
1458+
env.block.time,
1459+
Timestamp::from_nanos(1_571_797_419_879_305_533)
1460+
);
1461+
1462+
let env = envs.make();
1463+
assert_eq!(
1464+
env.contract.address.as_str(),
1465+
"food1jpev2csrppg792t22rn8z8uew8h3sjcpglcd0qv9g8gj8ky922ts74yrjj"
1466+
);
1467+
assert_eq!(env.block.height, 12_346);
1468+
assert_eq!(
1469+
env.block.time,
1470+
Timestamp::from_nanos(1_571_797_424_879_305_533)
1471+
);
1472+
1473+
let env = envs.make();
1474+
assert_eq!(
1475+
env.contract.address.as_str(),
1476+
"food1jpev2csrppg792t22rn8z8uew8h3sjcpglcd0qv9g8gj8ky922ts74yrjj"
1477+
);
1478+
assert_eq!(env.block.height, 12_347);
1479+
assert_eq!(
1480+
env.block.time,
1481+
Timestamp::from_nanos(1_571_797_429_879_305_533)
1482+
);
1483+
}
1484+
1485+
#[test]
1486+
fn envs_implements_iterator() {
1487+
let envs = Envs::new("food");
1488+
1489+
let result: Vec<_> = envs.into_iter().take(5).collect();
1490+
1491+
assert_eq!(
1492+
result[0].contract.address.as_str(),
1493+
"food1jpev2csrppg792t22rn8z8uew8h3sjcpglcd0qv9g8gj8ky922ts74yrjj"
1494+
);
1495+
assert_eq!(result[0].block.height, 12_345);
1496+
assert_eq!(
1497+
result[0].block.time,
1498+
Timestamp::from_nanos(1_571_797_419_879_305_533)
1499+
);
1500+
1501+
assert_eq!(
1502+
result[4].contract.address.as_str(),
1503+
"food1jpev2csrppg792t22rn8z8uew8h3sjcpglcd0qv9g8gj8ky922ts74yrjj"
1504+
);
1505+
assert_eq!(result[4].block.height, 12_349);
1506+
assert_eq!(
1507+
result[4].block.time,
1508+
Timestamp::from_nanos(1_571_797_439_879_305_533)
1509+
);
1510+
1511+
// Get a millions envs through iterator
1512+
let mut envs = Envs::new("yo");
1513+
let first = envs.next().unwrap();
1514+
let last = envs.take(1_000_000).last().unwrap();
1515+
assert_eq!(first.block.height, 12_345);
1516+
assert_eq!(last.block.height, 1_012_345);
1517+
assert_eq!(
1518+
last.block.time,
1519+
first.block.time.plus_seconds(1_000_000 * 5)
1520+
);
12831521
}
12841522

12851523
#[test]

packages/std/src/testing/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ pub use mock::DistributionQuerier;
1919
pub use mock::StakingQuerier;
2020
pub use mock::{
2121
mock_dependencies, mock_dependencies_with_balance, mock_dependencies_with_balances, mock_env,
22-
mock_wasmd_attr, BankQuerier, MockApi, MockQuerier, MockQuerierCustomHandlerResult,
23-
MOCK_CONTRACT_ADDR,
22+
mock_wasmd_attr, BankQuerier, Envs, EnvsOptions, MockApi, MockQuerier,
23+
MockQuerierCustomHandlerResult, MOCK_CONTRACT_ADDR,
2424
};
2525
#[cfg(feature = "ibc2")]
2626
pub use mock::{mock_ibc2_packet_recv, mock_ibc2_packet_timeout};

0 commit comments

Comments
 (0)