Repository: CosmWasm/cw-multi-test (rust)

Issue:
Remove `ProcessQueue` made-up sudo message for staking processing
The staking module has a made-up `ProcessQueue` sudo message to process the pending staking / unstakings.

This could be easily modified / improved, to call the `ProcessQueue` message handler under the hood before each query.

This can be done during / before every balance query. A little overkill, but since the queue is almost always empty.
It can also be done on `update_block`, after block updating.
Perhaps in both places for robustness, as the second call (empty queue) will be really cheap.


Pull request:
Deprecate sudo process queue
Closes #53.

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 970347d..d3d477f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
 
 [[package]]
 name = "ahash"
-version = "0.7.6"
+version = "0.7.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
+checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd"
 dependencies = [
  "getrandom",
  "once_cell",
@@ -60,9 +60,9 @@ checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
 
 [[package]]
 name = "base64"
-version = "0.21.4"
+version = "0.21.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2"
+checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
 
 [[package]]
 name = "base64ct"
@@ -158,9 +158,9 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-schema"
-version = "1.4.1"
+version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fef683a9c1c4eabd6d31515719d0d2cc66952c4c87f7eb192bfc90384517dc34"
+checksum = "0df41ea55f2946b6b43579659eec048cc2f66e8c8e2e3652fc5e5e476f673856"
 dependencies = [
  "cosmwasm-schema-derive",
  "schemars",
@@ -171,9 +171,9 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-schema-derive"
-version = "1.4.1"
+version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9567025acbb4c0c008178393eb53b3ac3c2e492c25949d3bf415b9cbe80772d8"
+checksum = "43609e92ce1b9368aa951b334dd354a2d0dd4d484931a5f83ae10e12a26c8ba9"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -204,18 +204,18 @@ dependencies = [
 
 [[package]]
 name = "cpufeatures"
-version = "0.2.9"
+version = "0.2.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1"
+checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0"
 dependencies = [
  "libc",
 ]
 
 [[package]]
 name = "crypto-bigint"
-version = "0.5.3"
+version = "0.5.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "740fe28e594155f10cfc383984cbefd529d7396050557148f79cb0f621204124"
+checksum = "28f85c3514d2a6e64160359b45a3918c3b4178bcbf4ae5d03ab2d02e521c479a"
 dependencies = [
  "generic-array",
  "rand_core 0.6.4",
@@ -268,9 +268,9 @@ dependencies = [
 
 [[package]]
 name = "cw-storage-plus"
-version = "1.1.0"
+version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f0e92a069d62067f3472c62e30adedb4cab1754725c0f2a682b3128d2bf3c79"
+checksum = "d5ff29294ee99373e2cd5fd21786a3c0ced99a52fec2ca347d565489c61b723c"
 dependencies = [
  "cosmwasm-std",
  "schemars",
@@ -350,9 +350,9 @@ dependencies = [
 
 [[package]]
 name = "dyn-clone"
-version = "1.0.14"
+version = "1.0.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23d2f3407d9a573d666de4b5bdf10569d73ca9478087346697dcbae6244bfbcd"
+checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d"
 
 [[package]]
 name = "ecdsa"
@@ -437,9 +437,9 @@ dependencies = [
 
 [[package]]
 name = "getrandom"
-version = "0.2.10"
+version = "0.2.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
+checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
 dependencies = [
  "cfg-if",
  "libc",
@@ -518,9 +518,9 @@ dependencies = [
 
 [[package]]
 name = "libc"
-version = "0.2.149"
+version = "0.2.150"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
+checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
 
 [[package]]
 name = "memchr"
@@ -597,7 +597,7 @@ dependencies = [
  "itertools",
  "proc-macro2",
  "quote",
- "syn 2.0.38",
+ "syn 2.0.39",
 ]
 
 [[package]]
@@ -648,9 +648,9 @@ checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
 
 [[package]]
 name = "schemars"
-version = "0.8.15"
+version = "0.8.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f7b0ce13155372a76ee2e1c5ffba1fe61ede73fbea5630d61eee6fac4929c0c"
+checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29"
 dependencies = [
  "dyn-clone",
  "schemars_derive",
@@ -660,9 +660,9 @@ dependencies = [
 
 [[package]]
 name = "schemars_derive"
-version = "0.8.15"
+version = "0.8.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c"
+checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -686,15 +686,15 @@ dependencies = [
 
 [[package]]
 name = "semver"
-version = "1.0.19"
+version = "1.0.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0"
+checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
 
 [[package]]
 name = "serde"
-version = "1.0.188"
+version = "1.0.192"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
+checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
 dependencies = [
  "serde_derive",
 ]
@@ -710,13 +710,13 @@ dependencies = [
 
 [[package]]
 name = "serde_derive"
-version = "1.0.188"
+version = "1.0.192"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
+checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.38",
+ "syn 2.0.39",
 ]
 
 [[package]]
@@ -732,9 +732,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.107"
+version = "1.0.108"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
+checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
 dependencies = [
  "itoa",
  "ryu",
@@ -810,9 +810,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.38"
+version = "2.0.39"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
+checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -821,22 +821,22 @@ dependencies = [
 
 [[package]]
 name = "thiserror"
-version = "1.0.49"
+version = "1.0.50"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4"
+checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.49"
+version = "1.0.50"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc"
+checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.38",
+ "syn 2.0.39",
 ]
 
 [[package]]
diff --git a/src/app.rs b/src/app.rs
index 9e3ed07..a52c3b5 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -271,7 +271,7 @@ where
     /// #  use serde::{Deserialize, Serialize};
     /// #  use cw_multi_test::{Contract, ContractWrapper};
     /// #
-    /// #  fn instantiate(_: DepsMut, _: Env, _: MessageInfo, _: Empty) -> Result<Response, StdError> {  
+    /// #  fn instantiate(_: DepsMut, _: Env, _: MessageInfo, _: Empty) -> Result<Response, StdError> {
     /// #    todo!()
     /// #  }
     /// #
@@ -282,7 +282,7 @@ where
     /// #  fn query(_deps: Deps, _env: Env, _msg: Empty) -> Result<Binary, StdError> {
     /// #    todo!()
     /// #  }
-    /// #  
+    /// #
     ///   pub fn contract() -> Box<dyn Contract<Empty>> {
     ///     // should return the contract
     /// #   Box::new(ContractWrapper::new(execute, instantiate, query))
@@ -334,11 +334,19 @@ where
     GovT: Gov,
 {
     pub fn set_block(&mut self, block: BlockInfo) {
+        self.router
+            .staking
+            .process_queue(&self.api, &mut self.storage, &self.router, &self.block)
+            .unwrap();
         self.block = block;
     }
 
     // this let's use use "next block" steps that add eg. one height and 5 seconds
     pub fn update_block<F: Fn(&mut BlockInfo)>(&mut self, action: F) {
+        self.router
+            .staking
+            .process_queue(&self.api, &mut self.storage, &self.router, &self.block)
+            .unwrap();
         action(&mut self.block);
     }
 
diff --git a/src/staking.rs b/src/staking.rs
index 8590657..5155c67 100644
--- a/src/staking.rs
+++ b/src/staking.rs
@@ -113,10 +113,23 @@ pub enum StakingSudo {
     /// Causes the unbonding queue to be processed.
     /// This needs to be triggered manually, since there is no good place to do this right now.
     /// In cosmos-sdk, this is done in `EndBlock`, but we don't have that here.
+    #[deprecated(note = "This is not needed anymore. Just call `update_block`")]
     ProcessQueue {},
 }
 
-pub trait Staking: Module<ExecT = StakingMsg, QueryT = StakingQuery, SudoT = StakingSudo> {}
+pub trait Staking: Module<ExecT = StakingMsg, QueryT = StakingQuery, SudoT = StakingSudo> {
+    /// This is called from the end blocker (`update_block` / `set_block`) to process the
+    /// staking queue.
+    /// Needed because unbonding has a waiting time.
+    /// If you're implementing a dummy staking module, this can be a no-op.
+    fn process_queue<ExecC, QueryC: CustomQuery>(
+        &self,
+        api: &dyn Api,
+        storage: &mut dyn Storage,
+        router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
+        block: &BlockInfo,
+    ) -> AnyResult<AppResponse>;
+}
 
 pub trait Distribution: Module<ExecT = DistributionMsg, QueryT = Empty, SudoT = Empty> {}
 
@@ -518,9 +531,86 @@ impl StakeKeeper {
         ensure!(percentage <= Decimal::one(), anyhow!("expected percentage"));
         Ok(())
     }
+
+    fn process_queue<ExecC, QueryC: CustomQuery>(
+        &self,
+        api: &dyn Api,
+        storage: &mut dyn Storage,
+        router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
+        block: &BlockInfo,
+    ) -> AnyResult<AppResponse> {
+        let staking_storage = prefixed_read(storage, NAMESPACE_STAKING);
+        let mut unbonding_queue = UNBONDING_QUEUE
+            .may_load(&staking_storage)?
+            .unwrap_or_default();
+        loop {
+            let mut staking_storage = prefixed(storage, NAMESPACE_STAKING);
+            match unbonding_queue.front() {
+                // assuming the queue is sorted by payout_at
+                Some(Unbonding { payout_at, .. }) if payout_at <= &block.time => {
+                    // remove from queue
+                    let Unbonding {
+                        delegator,
+                        validator,
+                        amount,
+                        ..
+                    } = unbonding_queue.pop_front().unwrap();
+
+                    // remove staking entry if it is empty
+                    let delegation = self
+                        .get_stake(&staking_storage, &delegator, &validator)?
+                        .map(|mut stake| {
+                            // add unbonding amounts
+                            stake.amount += unbonding_queue
+                                .iter()
+                                .filter(|u| u.delegator == delegator && u.validator == validator)
+                                .map(|u| u.amount)
+                                .sum::<Uint128>();
+                            stake
+                        });
+                    match delegation {
+                        Some(delegation) if delegation.amount.is_zero() => {
+                            STAKES.remove(&mut staking_storage, (&delegator, &validator));
+                        }
+                        None => STAKES.remove(&mut staking_storage, (&delegator, &validator)),
+                        _ => {}
+                    }
+
+                    let staking_info = Self::get_staking_info(&staking_storage)?;
+                    if !amount.is_zero() {
+                        router.execute(
+                            api,
+                            storage,
+                            block,
+                            self.module_addr.clone(),
+                            BankMsg::Send {
+                                to_address: delegator.into_string(),
+                                amount: vec![coin(amount.u128(), &staking_info.bonded_denom)],
+                            }
+                            .into(),
+                        )?;
+                    }
+                }
+                _ => break,
+            }
+        }
+        let mut staking_storage = prefixed(storage, NAMESPACE_STAKING);
+        UNBONDING_QUEUE.save(&mut staking_storage, &unbonding_queue)?;
+        Ok(AppResponse::default())
+    }
 }
 
-impl Staking for StakeKeeper {}
+impl Staking for StakeKeeper {
+    fn process_queue<ExecC, QueryC: CustomQuery>(
+        &self,
+        api: &dyn Api,
+        storage: &mut dyn Storage,
+        router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
+        block: &BlockInfo,
+    ) -> AnyResult<AppResponse> {
+        self.process_queue(api, storage, router, block)
+    }
+}
 
 impl Module for StakeKeeper {
     type ExecT = StakingMsg;
@@ -666,73 +756,8 @@ impl Module for StakeKeeper {
 
                 Ok(AppResponse::default())
             }
-            StakingSudo::ProcessQueue {} => {
-                let staking_storage = prefixed_read(storage, NAMESPACE_STAKING);
-                let mut unbonding_queue = UNBONDING_QUEUE
-                    .may_load(&staking_storage)?
-                    .unwrap_or_default();
-                loop {
-                    let mut staking_storage = prefixed(storage, NAMESPACE_STAKING);
-                    match unbonding_queue.front() {
-                        // assuming the queue is sorted by payout_at
-                        Some(Unbonding { payout_at, .. }) if payout_at <= &block.time => {
-                            // remove from queue
-                            let Unbonding {
-                                delegator,
-                                validator,
-                                amount,
-                                ..
-                            } = unbonding_queue.pop_front().unwrap();
-
-                            // remove staking entry if it is empty
-                            let delegation = self
-                                .get_stake(&staking_storage, &delegator, &validator)?
-                                .map(|mut stake| {
-                                    // add unbonding amounts
-                                    stake.amount += unbonding_queue
-                                        .iter()
-                                        .filter(|u| {
-                                            u.delegator == delegator && u.validator == validator
-                                        })
-                                        .map(|u| u.amount)
-                                        .sum::<Uint128>();
-                                    stake
-                                });
-                            match delegation {
-                                Some(delegation) if delegation.amount.is_zero() => {
-                                    STAKES.remove(&mut staking_storage, (&delegator, &validator));
-                                }
-                                None => {
-                                    STAKES.remove(&mut staking_storage, (&delegator, &validator))
-                                }
-                                _ => {}
-                            }
-
-                            let staking_info = Self::get_staking_info(&staking_storage)?;
-                            if !amount.is_zero() {
-                                router.execute(
-                                    api,
-                                    storage,
-                                    block,
-                                    self.module_addr.clone(),
-                                    BankMsg::Send {
-                                        to_address: delegator.into_string(),
-                                        amount: vec![coin(
-                                            amount.u128(),
-                                            &staking_info.bonded_denom,
-                                        )],
-                                    }
-                                    .into(),
-                                )?;
-                            }
-                        }
-                        _ => break,
-                    }
-                }
-                let mut staking_storage = prefixed(storage, NAMESPACE_STAKING);
-                UNBONDING_QUEUE.save(&mut staking_storage, &unbonding_queue)?;
-                Ok(AppResponse::default())
-            }
+            #[allow(deprecated)]
+            StakingSudo::ProcessQueue {} => self.process_queue(api, storage, router, block),
         }
     }
 
@@ -1633,12 +1658,11 @@ mod test {
             test_env
                 .router
                 .staking
-                .sudo(
+                .process_queue(
                     &test_env.api,
                     &mut test_env.store,
                     &test_env.router,
                     &test_env.block,
-                    StakingSudo::ProcessQueue {},
                 )
                 .unwrap();
 
@@ -2294,12 +2318,11 @@ mod test {
             test_env
                 .router
                 .staking
-                .sudo(
+                .process_queue(
                     &test_env.api,
                     &mut test_env.store,
                     &test_env.router,
                     &test_env.block,
-                    StakingSudo::ProcessQueue {},
                 )
                 .unwrap();
 
@@ -2328,12 +2351,11 @@ mod test {
             test_env
                 .router
                 .staking
-                .sudo(
+                .process_queue(
                     &test_env.api,
                     &mut test_env.store,
                     &test_env.router,
                     &test_env.block,
-                    StakingSudo::ProcessQueue {},
                 )
                 .unwrap();
 
@@ -2433,12 +2455,11 @@ mod test {
             test_env
                 .router
                 .staking
-                .sudo(
+                .process_queue(
                     &test_env.api,
                     &mut test_env.store,
                     &test_env.router,
                     &test_env.block,
-                    StakingSudo::ProcessQueue {},
                 )
                 .unwrap();
             let balance = QuerierWrapper::<Empty>::new(&test_env.router.querier(
diff --git a/src/wasm.rs b/src/wasm.rs
index 9e62110..4e7fe13 100644
--- a/src/wasm.rs
+++ b/src/wasm.rs
@@ -122,7 +122,7 @@ pub trait Wasm<ExecC, QueryC> {
 pub struct WasmKeeper<ExecC, QueryC> {
     /// Contract codes that stand for wasm code in real-life blockchain.
     code_base: Vec<Box<dyn Contract<ExecC, QueryC>>>,
-    /// Code data with code base identifier and additional attributes.  
+    /// Code data with code base identifier and additional attributes.
     code_data: Vec<CodeData>,
     /// Contract's address generator.
     address_generator: Box<dyn AddressGenerator>,
@@ -1244,10 +1244,7 @@ mod test {
         .unwrap_err();
 
         // Default error message from router when not found
-        assert_eq!(
-            StdError::not_found("cw_multi_test::wasm::ContractData"),
-            err.downcast().unwrap()
-        );
+        assert!(matches!(err.downcast().unwrap(), StdError::NotFound { .. }));
     }
 
     #[test]


Test patch:
diff --git a/tests/test_app_builder/test_with_staking.rs b/tests/test_app_builder/test_with_staking.rs
index a465a89..86febd4 100644
--- a/tests/test_app_builder/test_with_staking.rs
+++ b/tests/test_app_builder/test_with_staking.rs
@@ -1,10 +1,22 @@
 use crate::test_app_builder::MyKeeper;
-use cosmwasm_std::{Addr, Coin, StakingMsg, StakingQuery};
-use cw_multi_test::{AppBuilder, Executor, Staking, StakingSudo};
+use cosmwasm_std::{Addr, Api, BlockInfo, Coin, CustomQuery, StakingMsg, StakingQuery, Storage};
+use cw_multi_test::error::AnyResult;
+use cw_multi_test::{AppBuilder, AppResponse, CosmosRouter, Executor, Staking, StakingSudo};
 
 type MyStakeKeeper = MyKeeper<StakingMsg, StakingQuery, StakingSudo>;
 
-impl Staking for MyStakeKeeper {}
+impl Staking for MyStakeKeeper {
+    fn process_queue<ExecC, QueryC: CustomQuery>(
+        &self,
+        api: &dyn Api,
+        storage: &mut dyn Storage,
+        router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
+        block: &BlockInfo,
+    ) -> AnyResult<AppResponse> {
+        let _ = (api, storage, router, block);
+        todo!()
+    }
+}
 
 const EXECUTE_MSG: &str = "staking execute called";
 const QUERY_MSG: &str = "staking query called";