pallet-treasury: Ensure we respect max_amount for spend across batch calls (#13468)

* `pallet-treasury`: Ensure we respect `max_amount` for spend across batch calls

When calling `spend` the origin defines the `max_amount` of tokens it is allowed to spend. The
problem is that someone can send a `batch(spend, spend)` to circumvent this restriction as we don't
check across different calls that the `max_amount` is respected. This pull request fixes this
behavior by introducing a so-called dispatch context. This dispatch context is created once per
outer most `dispatch` call. For more information see the docs in this pr. The treasury then uses
this dispatch context to attach information about already spent funds per `max_amount` (we assume
that each origin has a different `max_amount` configured). So, a `batch(spend, spend)` is now
checked to stay inside the allowed spending bounds.

Fixes: https://github.com/paritytech/substrate/issues/13167

* Import `Box` for wasm

* FMT
This commit is contained in:
Bastian Köcher
2023-02-27 18:49:16 +01:00
committed by GitHub
parent 85a5a5db13
commit 6aa4127a74
9 changed files with 353 additions and 24 deletions
+30 -2
View File
@@ -16,9 +16,12 @@
// limitations under the License.
use frame_support::{
assert_ok,
dispatch::{
DispatchClass, DispatchInfo, GetDispatchInfo, Parameter, Pays, UnfilteredDispatchable,
DispatchClass, DispatchInfo, Dispatchable, GetDispatchInfo, Parameter, Pays,
UnfilteredDispatchable,
},
dispatch_context::with_context,
pallet_prelude::{StorageInfoTrait, ValueQuery},
storage::unhashed,
traits::{
@@ -102,6 +105,7 @@ pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use sp_runtime::DispatchResult;
type BalanceOf<T> = <T as Config>::Balance;
@@ -227,6 +231,12 @@ pub mod pallet {
pub fn foo_no_post_info(_origin: OriginFor<T>) -> DispatchResult {
Ok(())
}
#[pallet::call_index(3)]
#[pallet::weight(1)]
pub fn check_for_dispatch_context(_origin: OriginFor<T>) -> DispatchResult {
with_context::<(), _>(|_| ()).ok_or_else(|| DispatchError::Unavailable)
}
}
#[pallet::error]
@@ -713,7 +723,7 @@ fn call_expand() {
assert_eq!(call_foo.get_call_name(), "foo");
assert_eq!(
pallet::Call::<Runtime>::get_call_names(),
&["foo", "foo_storage_layer", "foo_no_post_info"],
&["foo", "foo_storage_layer", "foo_no_post_info", "check_for_dispatch_context"],
);
}
@@ -1933,3 +1943,21 @@ fn test_storage_alias() {
);
})
}
#[test]
fn test_dispatch_context() {
TestExternalities::default().execute_with(|| {
// By default there is no context
assert!(with_context::<(), _>(|_| ()).is_none());
// When not using `dispatch`, there should be no dispatch context
assert_eq!(
DispatchError::Unavailable,
Example::check_for_dispatch_context(RuntimeOrigin::root()).unwrap_err(),
);
// When using `dispatch`, there should be a dispatch context
assert_ok!(RuntimeCall::from(pallet::Call::<Runtime>::check_for_dispatch_context {})
.dispatch(RuntimeOrigin::root()));
});
}