Make automatic storage deposits resistant against changing deposit prices (#12083)

* Require `FixedPointOperand` for Balances

* Delay deposit calculation

* Make refunds pro rata of consumed storage

* Add storage migration

* Fix clippy

* Add liquidity checks

* Fixe delayed deposit limit enforcement

* Defer charges

* Import Vec

* Add try-runtime hooks for migration

* Fix warning

* Adapt to new OnRuntimeUpgrade trait

* Apply suggestions from code review

Co-authored-by: Sasha Gryaznov <hi@agryaznov.com>

* fmt

* Apply suggestions from code review

Co-authored-by: Sasha Gryaznov <hi@agryaznov.com>

* More suggestions from code review

Co-authored-by: Sasha Gryaznov <hi@agryaznov.com>
This commit is contained in:
Alexander Theißen
2022-09-21 19:31:22 +02:00
committed by GitHub
parent cb82064cb8
commit 857c3bf37b
12 changed files with 957 additions and 312 deletions
+347 -39
View File
@@ -23,6 +23,7 @@ use crate::{
},
exec::{FixSizedKey, Frame},
storage::Storage,
tests::test_utils::{get_contract, get_contract_checked},
wasm::{PrefabWasmModule, ReturnCode as RuntimeReturnCode},
weights::WeightInfo,
BalanceOf, Code, CodeStorage, Config, ContractInfoOf, DefaultAddressGenerator,
@@ -36,8 +37,8 @@ use frame_support::{
parameter_types,
storage::child,
traits::{
BalanceStatus, ConstU32, ConstU64, Contains, Currency, Get, OnIdle, OnInitialize,
ReservableCurrency,
BalanceStatus, ConstU32, ConstU64, Contains, Currency, Get, LockableCurrency, OnIdle,
OnInitialize, ReservableCurrency, WithdrawReasons,
},
weights::{constants::WEIGHT_PER_SECOND, Weight},
};
@@ -76,7 +77,9 @@ frame_support::construct_runtime!(
#[macro_use]
pub mod test_utils {
use super::{Balances, Hash, SysConfig, Test};
use crate::{exec::AccountIdOf, storage::Storage, CodeHash, Config, ContractInfoOf, Nonce};
use crate::{
exec::AccountIdOf, storage::Storage, CodeHash, Config, ContractInfo, ContractInfoOf, Nonce,
};
use codec::Encode;
use frame_support::traits::Currency;
@@ -97,6 +100,12 @@ pub mod test_utils {
pub fn get_balance(who: &AccountIdOf<Test>) -> u64 {
Balances::free_balance(who)
}
pub fn get_contract(addr: &AccountIdOf<Test>) -> ContractInfo<Test> {
get_contract_checked(addr).unwrap()
}
pub fn get_contract_checked(addr: &AccountIdOf<Test>) -> Option<ContractInfo<Test>> {
ContractInfoOf::<Test>::get(addr)
}
pub fn hash<S: Encode>(s: &S) -> <<Test as SysConfig>::Hashing as Hash>::Output {
<<Test as SysConfig>::Hashing as Hash>::hash_of(s)
}
@@ -105,6 +114,7 @@ pub mod test_utils {
assert_eq!(u32::from_le_bytes($x.data[..].try_into().unwrap()), $y as u32);
}};
}
macro_rules! assert_refcount {
( $code_hash:expr , $should:expr $(,)? ) => {{
let is = crate::OwnerInfoOf::<Test>::get($code_hash).map(|m| m.refcount()).unwrap();
@@ -693,7 +703,7 @@ fn instantiate_unique_trie_id() {
vec![],
vec![],
));
let trie_id = ContractInfoOf::<Test>::get(&addr).unwrap().trie_id;
let trie_id = get_contract(&addr).trie_id;
// Try to instantiate it again without termination should yield an error.
assert_err_ignore_postinfo!(
@@ -731,7 +741,7 @@ fn instantiate_unique_trie_id() {
));
// Trie ids shouldn't match or we might have a collision
assert_ne!(trie_id, ContractInfoOf::<Test>::get(&addr).unwrap().trie_id);
assert_ne!(trie_id, get_contract(&addr).trie_id);
});
}
@@ -752,7 +762,7 @@ fn storage_max_value_limit() {
vec![],
));
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
ContractInfoOf::<Test>::get(&addr).unwrap();
get_contract(&addr);
// Call contract with allowed storage value.
assert_ok!(Contracts::call(
@@ -962,7 +972,7 @@ fn cannot_self_destruct_through_draning() {
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
// Check that the BOB contract has been instantiated.
assert_matches!(ContractInfoOf::<Test>::get(&addr), Some(_));
get_contract(&addr);
// Call BOB which makes it send all funds to the zero address
// The contract code asserts that the transfer was successful
@@ -1003,11 +1013,11 @@ fn cannot_self_destruct_through_storage_refund_after_price_change() {
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
// Check that the BOB contract has been instantiated and has the minimum balance
let info = ContractInfoOf::<Test>::get(&addr).unwrap();
assert_eq!(info.storage_deposit, min_balance);
assert_eq!(get_contract(&addr).total_deposit(), min_balance);
assert_eq!(get_contract(&addr).extra_deposit(), 0);
assert_eq!(<Test as Config>::Currency::total_balance(&addr), min_balance);
// Create 100 bytes of storage with a price of per byte
// Create 100 bytes of storage with a price of per byte and a single storage item of price 2
assert_ok!(Contracts::call(
RuntimeOrigin::signed(ALICE),
addr.clone(),
@@ -1016,9 +1026,10 @@ fn cannot_self_destruct_through_storage_refund_after_price_change() {
None,
100u32.to_le_bytes().to_vec()
));
assert_eq!(get_contract(&addr).total_deposit(), min_balance + 102);
// Increase the byte price and trigger a refund. This could potentially destroy the account
// because the refund removes the reserved existential deposit. This should not happen.
// Increase the byte price and trigger a refund. This should not have any influence because
// the removal is pro rata and exactly those 100 bytes should have been removed.
DEPOSIT_PER_BYTE.with(|c| *c.borrow_mut() = 500);
assert_ok!(Contracts::call(
RuntimeOrigin::signed(ALICE),
@@ -1032,8 +1043,9 @@ fn cannot_self_destruct_through_storage_refund_after_price_change() {
// Make sure the account wasn't removed by the refund
assert_eq!(
<Test as Config>::Currency::total_balance(&addr),
<Test as Config>::Currency::minimum_balance(),
get_contract(&addr).total_deposit(),
);
assert_eq!(get_contract(&addr).extra_deposit(), 2,);
});
}
@@ -1086,9 +1098,6 @@ fn cannot_self_destruct_by_refund_after_slash() {
// Make sure the account kept the minimum balance and was not destroyed
assert_eq!(<Test as Config>::Currency::total_balance(&addr), min_balance);
// even though it was not charged it is still substracted from the storage deposit tracker
assert_eq!(ContractInfoOf::<Test>::get(&addr).unwrap().storage_deposit, 550);
assert_eq!(
System::events(),
vec![
@@ -1142,7 +1151,7 @@ fn cannot_self_destruct_while_live() {
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
// Check that the BOB contract has been instantiated.
assert_matches!(ContractInfoOf::<Test>::get(&addr), Some(_));
get_contract(&addr);
// Call BOB with input data, forcing it make a recursive call to itself to
// self-destruct, resulting in a trap.
@@ -1159,7 +1168,7 @@ fn cannot_self_destruct_while_live() {
);
// Check that BOB is still there.
assert_matches!(ContractInfoOf::<Test>::get(&addr), Some(_));
get_contract(&addr);
});
}
@@ -1183,7 +1192,7 @@ fn self_destruct_works() {
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
// Check that the BOB contract has been instantiated.
assert_matches!(ContractInfoOf::<Test>::get(&addr), Some(_));
get_contract(&addr);
// Drop all previous events
initialize_block(2);
@@ -1198,7 +1207,7 @@ fn self_destruct_works() {
assert_refcount!(&code_hash, 0);
// Check that account is gone
assert!(ContractInfoOf::<Test>::get(&addr).is_none());
assert!(get_contract_checked(&addr).is_none());
assert_eq!(Balances::total_balance(&addr), 0);
// check that the beneficiary (django) got remaining balance
@@ -1289,7 +1298,7 @@ fn destroy_contract_and_transfer_funds() {
let addr_charlie = Contracts::contract_address(&addr_bob, &callee_code_hash, &[0x47, 0x11]);
// Check that the CHARLIE contract has been instantiated.
assert_matches!(ContractInfoOf::<Test>::get(&addr_charlie), Some(_));
get_contract(&addr_charlie);
// Call BOB, which calls CHARLIE, forcing CHARLIE to self-destruct.
assert_ok!(Contracts::call(
@@ -1302,7 +1311,7 @@ fn destroy_contract_and_transfer_funds() {
));
// Check that CHARLIE has moved on to the great beyond (ie. died).
assert!(ContractInfoOf::<Test>::get(&addr_charlie).is_none());
assert!(get_contract_checked(&addr_charlie).is_none());
});
}
@@ -1862,7 +1871,7 @@ fn lazy_removal_works() {
),);
let addr = Contracts::contract_address(&ALICE, &hash, &[]);
let info = <ContractInfoOf<Test>>::get(&addr).unwrap();
let info = get_contract(&addr);
let trie = &info.child_trie_info();
// Put value into the contracts child trie
@@ -1931,7 +1940,7 @@ fn lazy_batch_removal_works() {
),);
let addr = Contracts::contract_address(&ALICE, &hash, &[i]);
let info = <ContractInfoOf<Test>>::get(&addr).unwrap();
let info = get_contract(&addr);
let trie = &info.child_trie_info();
// Put value into the contracts child trie
@@ -1993,7 +2002,7 @@ fn lazy_removal_partial_remove_works() {
),);
let addr = Contracts::contract_address(&ALICE, &hash, &[]);
let info = <ContractInfoOf<Test>>::get(&addr).unwrap();
let info = get_contract(&addr);
// Put value into the contracts child trie
for val in &vals {
@@ -2105,7 +2114,7 @@ fn lazy_removal_does_no_run_on_low_remaining_weight() {
),);
let addr = Contracts::contract_address(&ALICE, &hash, &[]);
let info = <ContractInfoOf<Test>>::get(&addr).unwrap();
let info = get_contract(&addr);
let trie = &info.child_trie_info();
// Put value into the contracts child trie
@@ -2173,7 +2182,7 @@ fn lazy_removal_does_not_use_all_weight() {
),);
let addr = Contracts::contract_address(&ALICE, &hash, &[]);
let info = <ContractInfoOf<Test>>::get(&addr).unwrap();
let info = get_contract(&addr);
let (weight_per_key, max_keys) = Storage::<Test>::deletion_budget(1, weight_limit);
// We create a contract with one less storage item than we can remove within the limit
@@ -2264,7 +2273,7 @@ fn deletion_queue_full() {
);
// Contract should exist because removal failed
<ContractInfoOf<Test>>::get(&addr).unwrap();
get_contract(&addr);
});
}
@@ -2909,7 +2918,7 @@ fn instantiate_with_zero_balance_works() {
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
// Check that the BOB contract has been instantiated.
assert_matches!(ContractInfoOf::<Test>::get(&addr), Some(_));
get_contract(&addr);
// Make sure the account exists even though no free balance was send
assert_eq!(<Test as Config>::Currency::free_balance(&addr), 0,);
@@ -3002,7 +3011,7 @@ fn instantiate_with_below_existential_deposit_works() {
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
// Check that the BOB contract has been instantiated.
assert_matches!(ContractInfoOf::<Test>::get(&addr), Some(_));
get_contract(&addr);
// Make sure the account exists even though no free balance was send
assert_eq!(<Test as Config>::Currency::free_balance(&addr), 50,);
@@ -3114,7 +3123,7 @@ fn storage_deposit_works() {
// 4 is for creating 2 storage items
let charged0 = 4 + 1_000 + 5_000;
deposit += charged0;
assert_eq!(<ContractInfoOf<Test>>::get(&addr).unwrap().storage_deposit, deposit);
assert_eq!(get_contract(&addr).total_deposit(), deposit);
// Add more storage (but also remove some)
assert_ok!(Contracts::call(
@@ -3127,7 +3136,7 @@ fn storage_deposit_works() {
));
let charged1 = 1_000 - 100;
deposit += charged1;
assert_eq!(<ContractInfoOf<Test>>::get(&addr).unwrap().storage_deposit, deposit);
assert_eq!(get_contract(&addr).total_deposit(), deposit);
// Remove more storage (but also add some)
assert_ok!(Contracts::call(
@@ -3138,9 +3147,10 @@ fn storage_deposit_works() {
None,
(2_100u32, 900u32).encode(),
));
let refunded0 = 4_000 - 100;
// -1 for numeric instability
let refunded0 = 4_000 - 100 - 1;
deposit -= refunded0;
assert_eq!(<ContractInfoOf<Test>>::get(&addr).unwrap().storage_deposit, deposit);
assert_eq!(get_contract(&addr).total_deposit(), deposit);
assert_eq!(
System::events(),
@@ -3253,7 +3263,7 @@ fn set_code_extrinsic() {
// Drop previous events
initialize_block(2);
assert_eq!(<ContractInfoOf<Test>>::get(&addr).unwrap().code_hash, code_hash);
assert_eq!(get_contract(&addr).code_hash, code_hash);
assert_refcount!(&code_hash, 1);
assert_refcount!(&new_code_hash, 0);
@@ -3262,7 +3272,7 @@ fn set_code_extrinsic() {
Contracts::set_code(RuntimeOrigin::signed(ALICE), addr.clone(), new_code_hash),
sp_runtime::traits::BadOrigin,
);
assert_eq!(<ContractInfoOf<Test>>::get(&addr).unwrap().code_hash, code_hash);
assert_eq!(get_contract(&addr).code_hash, code_hash);
assert_refcount!(&code_hash, 1);
assert_refcount!(&new_code_hash, 0);
assert_eq!(System::events(), vec![],);
@@ -3272,7 +3282,7 @@ fn set_code_extrinsic() {
Contracts::set_code(RuntimeOrigin::root(), BOB, new_code_hash),
<Error<Test>>::ContractNotFound,
);
assert_eq!(<ContractInfoOf<Test>>::get(&addr).unwrap().code_hash, code_hash);
assert_eq!(get_contract(&addr).code_hash, code_hash);
assert_refcount!(&code_hash, 1);
assert_refcount!(&new_code_hash, 0);
assert_eq!(System::events(), vec![],);
@@ -3282,14 +3292,14 @@ fn set_code_extrinsic() {
Contracts::set_code(RuntimeOrigin::root(), addr.clone(), Default::default()),
<Error<Test>>::CodeNotFound,
);
assert_eq!(<ContractInfoOf<Test>>::get(&addr).unwrap().code_hash, code_hash);
assert_eq!(get_contract(&addr).code_hash, code_hash);
assert_refcount!(&code_hash, 1);
assert_refcount!(&new_code_hash, 0);
assert_eq!(System::events(), vec![],);
// successful call
assert_ok!(Contracts::set_code(RuntimeOrigin::root(), addr.clone(), new_code_hash));
assert_eq!(<ContractInfoOf<Test>>::get(&addr).unwrap().code_hash, new_code_hash);
assert_eq!(get_contract(&addr).code_hash, new_code_hash);
assert_refcount!(&code_hash, 0);
assert_refcount!(&new_code_hash, 1);
assert_eq!(
@@ -3639,3 +3649,301 @@ fn set_code_hash() {
);
});
}
#[test]
fn storage_deposit_limit_is_enforced() {
let (wasm, code_hash) = compile_module::<Test>("store").unwrap();
ExtBuilder::default().existential_deposit(200).build().execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
let min_balance = <Test as Config>::Currency::minimum_balance();
// Instantiate the BOB contract.
assert_ok!(Contracts::instantiate_with_code(
RuntimeOrigin::signed(ALICE),
0,
GAS_LIMIT,
None,
wasm,
vec![],
vec![],
));
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
// Check that the BOB contract has been instantiated and has the minimum balance
assert_eq!(get_contract(&addr).total_deposit(), min_balance);
assert_eq!(<Test as Config>::Currency::total_balance(&addr), min_balance);
// Create 100 bytes of storage with a price of per byte
assert_err_ignore_postinfo!(
Contracts::call(
RuntimeOrigin::signed(ALICE),
addr.clone(),
0,
GAS_LIMIT,
Some(codec::Compact(1)),
100u32.to_le_bytes().to_vec()
),
<Error<Test>>::StorageDepositLimitExhausted,
);
});
}
#[test]
fn storage_deposit_limit_is_enforced_late() {
let (wasm_caller, code_hash_caller) =
compile_module::<Test>("create_storage_and_call").unwrap();
let (wasm_callee, code_hash_callee) = compile_module::<Test>("store").unwrap();
ExtBuilder::default().existential_deposit(200).build().execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
// Create both contracts: Constructors do nothing.
assert_ok!(Contracts::instantiate_with_code(
RuntimeOrigin::signed(ALICE),
0,
GAS_LIMIT,
None,
wasm_caller,
vec![],
vec![],
));
let addr_caller = Contracts::contract_address(&ALICE, &code_hash_caller, &[]);
assert_ok!(Contracts::instantiate_with_code(
RuntimeOrigin::signed(ALICE),
0,
GAS_LIMIT,
None,
wasm_callee,
vec![],
vec![],
));
let addr_callee = Contracts::contract_address(&ALICE, &code_hash_callee, &[]);
// Create 100 bytes of storage with a price of per byte
// This is 100 Balance + 2 Balance for the item
assert_ok!(Contracts::call(
RuntimeOrigin::signed(ALICE),
addr_callee.clone(),
0,
GAS_LIMIT,
Some(codec::Compact(102)),
100u32.to_le_bytes().to_vec()
));
// We do not remove any storage but require 14 bytes of storage for the new
// storage created in the immediate contract.
assert_err_ignore_postinfo!(
Contracts::call(
RuntimeOrigin::signed(ALICE),
addr_caller.clone(),
0,
GAS_LIMIT,
Some(codec::Compact(5)),
100u32
.to_le_bytes()
.as_ref()
.iter()
.chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee))
.cloned()
.collect(),
),
<Error<Test>>::StorageDepositLimitExhausted,
);
// Allow for the additional 14 bytes but demand an additional byte in the callee contract.
assert_err_ignore_postinfo!(
Contracts::call(
RuntimeOrigin::signed(ALICE),
addr_caller.clone(),
0,
GAS_LIMIT,
Some(codec::Compact(14)),
101u32
.to_le_bytes()
.as_ref()
.iter()
.chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee))
.cloned()
.collect(),
),
<Error<Test>>::StorageDepositLimitExhausted,
);
// Refund in the callee contract but not enough to cover the 14 balance required by the
// caller.
assert_err_ignore_postinfo!(
Contracts::call(
RuntimeOrigin::signed(ALICE),
addr_caller.clone(),
0,
GAS_LIMIT,
Some(codec::Compact(0)),
87u32
.to_le_bytes()
.as_ref()
.iter()
.chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee))
.cloned()
.collect(),
),
<Error<Test>>::StorageDepositLimitExhausted,
);
let _ = Balances::make_free_balance_be(&ALICE, 1_000);
// Send more than the sender has balance.
assert_err_ignore_postinfo!(
Contracts::call(
RuntimeOrigin::signed(ALICE),
addr_caller.clone(),
0,
GAS_LIMIT,
Some(codec::Compact(50)),
1_200u32
.to_le_bytes()
.as_ref()
.iter()
.chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee))
.cloned()
.collect(),
),
<Error<Test>>::StorageDepositLimitExhausted,
);
// Same as above but allow for the additional balance.
assert_ok!(Contracts::call(
RuntimeOrigin::signed(ALICE),
addr_caller.clone(),
0,
GAS_LIMIT,
Some(codec::Compact(1)),
87u32
.to_le_bytes()
.as_ref()
.iter()
.chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee))
.cloned()
.collect(),
));
});
}
#[test]
fn deposit_limit_honors_liquidity_restrictions() {
let (wasm, code_hash) = compile_module::<Test>("store").unwrap();
ExtBuilder::default().existential_deposit(200).build().execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
let _ = Balances::deposit_creating(&BOB, 1_000);
let min_balance = <Test as Config>::Currency::minimum_balance();
// Instantiate the BOB contract.
assert_ok!(Contracts::instantiate_with_code(
RuntimeOrigin::signed(ALICE),
0,
GAS_LIMIT,
None,
wasm,
vec![],
vec![],
));
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
// Check that the contract has been instantiated and has the minimum balance
assert_eq!(get_contract(&addr).total_deposit(), min_balance);
assert_eq!(<Test as Config>::Currency::total_balance(&addr), min_balance);
// check that the lock ins honored
Balances::set_lock([0; 8], &BOB, 1_000, WithdrawReasons::TRANSFER);
assert_err_ignore_postinfo!(
Contracts::call(
RuntimeOrigin::signed(BOB),
addr.clone(),
0,
GAS_LIMIT,
Some(codec::Compact(200)),
100u32.to_le_bytes().to_vec()
),
<Error<Test>>::StorageDepositNotEnoughFunds,
);
assert_eq!(Balances::free_balance(&BOB), 1_000);
});
}
#[test]
fn deposit_limit_honors_existential_deposit() {
let (wasm, code_hash) = compile_module::<Test>("store").unwrap();
ExtBuilder::default().existential_deposit(200).build().execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
let _ = Balances::deposit_creating(&BOB, 1_000);
let min_balance = <Test as Config>::Currency::minimum_balance();
// Instantiate the BOB contract.
assert_ok!(Contracts::instantiate_with_code(
RuntimeOrigin::signed(ALICE),
0,
GAS_LIMIT,
None,
wasm,
vec![],
vec![],
));
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
// Check that the contract has been instantiated and has the minimum balance
assert_eq!(get_contract(&addr).total_deposit(), min_balance);
assert_eq!(<Test as Config>::Currency::total_balance(&addr), min_balance);
// check that the deposit can't bring the account below the existential deposit
assert_err_ignore_postinfo!(
Contracts::call(
RuntimeOrigin::signed(BOB),
addr.clone(),
0,
GAS_LIMIT,
Some(codec::Compact(900)),
100u32.to_le_bytes().to_vec()
),
<Error<Test>>::StorageDepositNotEnoughFunds,
);
assert_eq!(Balances::free_balance(&BOB), 1_000);
});
}
#[test]
fn deposit_limit_honors_min_leftover() {
let (wasm, code_hash) = compile_module::<Test>("store").unwrap();
ExtBuilder::default().existential_deposit(200).build().execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
let _ = Balances::deposit_creating(&BOB, 1_000);
let min_balance = <Test as Config>::Currency::minimum_balance();
// Instantiate the BOB contract.
assert_ok!(Contracts::instantiate_with_code(
RuntimeOrigin::signed(ALICE),
0,
GAS_LIMIT,
None,
wasm,
vec![],
vec![],
));
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
// Check that the contract has been instantiated and has the minimum balance
assert_eq!(get_contract(&addr).total_deposit(), min_balance);
assert_eq!(<Test as Config>::Currency::total_balance(&addr), min_balance);
// check that the minumum leftover (value send) is considered
assert_err_ignore_postinfo!(
Contracts::call(
RuntimeOrigin::signed(BOB),
addr.clone(),
400,
GAS_LIMIT,
Some(codec::Compact(500)),
100u32.to_le_bytes().to_vec()
),
<Error<Test>>::StorageDepositNotEnoughFunds,
);
assert_eq!(Balances::free_balance(&BOB), 1_000);
});
}