@@ -390,17 +390,166 @@ fn validate_length(bytes: &[u8]) -> StdResult<()> {
390390/// // `env3` is one block and 5.5 seconds later
391391/// ```
392392pub 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]
0 commit comments