contracts: Don't rely on reserved balances keeping an account alive (#13369)

* Move storage deposits to their own account

* Take ed for contract's account from origin

* Apply suggestions from code review

Co-authored-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
Co-authored-by: Sasha Gryaznov <hi@agryaznov.com>

* Update stale docs

* Use 16 bytes prefix for address derivation

* Update frame/contracts/src/address.rs

Co-authored-by: Cyrill Leutwiler <bigcyrill@hotmail.com>

* Fix merge

* ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts

* Update frame/contracts/primitives/src/lib.rs

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

---------

Co-authored-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
Co-authored-by: Sasha Gryaznov <hi@agryaznov.com>
Co-authored-by: command-bot <>
This commit is contained in:
Alexander Theißen
2023-02-16 16:58:28 +01:00
committed by GitHub
parent 292e5ee4e7
commit 330484bb19
11 changed files with 2109 additions and 2184 deletions
+209 -309
View File
@@ -22,12 +22,11 @@ use crate::{
Result as ExtensionResult, RetVal, ReturnFlags, SysConfig,
},
exec::{FixSizedKey, Frame},
storage::Storage,
tests::test_utils::{get_contract, get_contract_checked},
wasm::{Determinism, PrefabWasmModule, ReturnCode as RuntimeReturnCode},
weights::WeightInfo,
BalanceOf, Code, CodeStorage, Config, ContractInfoOf, DefaultAddressGenerator, DeletionQueue,
Error, Pallet, Schedule,
BalanceOf, Code, CodeStorage, Config, ContractInfo, ContractInfoOf, DefaultAddressGenerator,
DeletionQueue, Error, Pallet, Schedule,
};
use assert_matches::assert_matches;
use codec::Encode;
@@ -37,8 +36,8 @@ use frame_support::{
parameter_types,
storage::child,
traits::{
BalanceStatus, ConstU32, ConstU64, Contains, Currency, Get, LockableCurrency, OnIdle,
OnInitialize, ReservableCurrency, WithdrawReasons,
ConstU32, ConstU64, Contains, Currency, ExistenceRequirement, Get, LockableCurrency,
OnIdle, OnInitialize, ReservableCurrency, WithdrawReasons,
},
weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight},
};
@@ -51,7 +50,7 @@ use sp_runtime::{
traits::{BlakeTwo256, Convert, Hash, IdentityLookup},
AccountId32,
};
use std::sync::Arc;
use std::{ops::Deref, sync::Arc};
use crate as pallet_contracts;
@@ -76,9 +75,7 @@ frame_support::construct_runtime!(
#[macro_use]
pub mod test_utils {
use super::{Balances, Hash, SysConfig, Test};
use crate::{
exec::AccountIdOf, storage::Storage, CodeHash, Config, ContractInfo, ContractInfoOf, Nonce,
};
use crate::{exec::AccountIdOf, CodeHash, Config, ContractInfo, ContractInfoOf, Nonce};
use codec::Encode;
use frame_support::traits::Currency;
@@ -87,9 +84,8 @@ pub mod test_utils {
*counter += 1;
*counter
});
let trie_id = Storage::<Test>::generate_trie_id(address, nonce);
set_balance(address, <Test as Config>::Currency::minimum_balance() * 10);
let contract = Storage::<Test>::new_contract(&address, trie_id, code_hash).unwrap();
let contract = <ContractInfo<Test>>::new(&address, nonce, code_hash).unwrap();
<ContractInfoOf<Test>>::insert(address, contract);
}
pub fn set_balance(who: &AccountIdOf<Test>, amount: u64) {
@@ -518,7 +514,7 @@ fn calling_plain_account_fails() {
fn instantiate_and_call_and_deposit_event() {
let (wasm, code_hash) = compile_module::<Test>("event_and_return_on_deploy").unwrap();
ExtBuilder::default().existential_deposit(500).build().execute_with(|| {
ExtBuilder::default().existential_deposit(1).build().execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
let min_balance = <Test as Config>::Currency::minimum_balance();
let value = 100;
@@ -551,9 +547,36 @@ fn instantiate_and_call_and_deposit_event() {
.account_id;
assert!(ContractInfoOf::<Test>::contains_key(&addr));
let contract = get_contract(&addr);
let deposit_account = contract.deposit_account().deref();
assert_eq!(
System::events(),
vec![
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::System(frame_system::Event::NewAccount {
account: deposit_account.clone(),
}),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::Endowed {
account: deposit_account.clone(),
free_balance: 131,
}),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
from: ALICE,
to: deposit_account.clone(),
amount: 131,
}),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::System(frame_system::Event::NewAccount {
@@ -578,14 +601,6 @@ fn instantiate_and_call_and_deposit_event() {
}),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::Reserved {
who: addr.clone(),
amount: min_balance,
}),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
@@ -823,7 +838,7 @@ fn deploy_and_call_other_contract() {
let (caller_wasm, _caller_code_hash) = compile_module::<Test>("caller_contract").unwrap();
let (callee_wasm, callee_code_hash) = compile_module::<Test>("return_with_data").unwrap();
ExtBuilder::default().existential_deposit(500).build().execute_with(|| {
ExtBuilder::default().existential_deposit(1).build().execute_with(|| {
let min_balance = <Test as Config>::Currency::minimum_balance();
// Create
@@ -841,18 +856,7 @@ fn deploy_and_call_other_contract() {
.result
.unwrap()
.account_id;
Contracts::bare_instantiate(
ALICE,
100_000,
GAS_LIMIT,
None,
Code::Upload(callee_wasm),
0u32.to_le_bytes().encode(),
vec![42],
false,
)
.result
.unwrap();
Contracts::bare_upload_code(ALICE, callee_wasm, None, Determinism::Deterministic).unwrap();
let callee_addr = Contracts::contract_address(
&caller_addr,
@@ -875,9 +879,36 @@ fn deploy_and_call_other_contract() {
callee_code_hash.as_ref().to_vec(),
));
let callee = get_contract(&callee_addr);
let deposit_account = callee.deposit_account().deref();
assert_eq!(
System::events(),
vec![
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::System(frame_system::Event::NewAccount {
account: deposit_account.clone(),
}),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::Endowed {
account: deposit_account.clone(),
free_balance: 131,
}),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
from: ALICE,
to: deposit_account.clone(),
amount: 131,
}),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::System(frame_system::Event::NewAccount {
@@ -902,20 +933,12 @@ fn deploy_and_call_other_contract() {
}),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::Reserved {
who: callee_addr.clone(),
amount: min_balance,
}),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
from: caller_addr.clone(),
to: callee_addr.clone(),
amount: 32768, // hard coded in wasm
amount: 32768 // hardcoded in wasm
}),
topics: vec![],
},
@@ -998,6 +1021,46 @@ fn delegate_call() {
});
}
#[test]
fn transfer_allow_death_cannot_kill_account() {
let (wasm, _code_hash) = compile_module::<Test>("dummy").unwrap();
ExtBuilder::default().existential_deposit(200).build().execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
// Instantiate the BOB contract.
let addr = Contracts::bare_instantiate(
ALICE,
1_000,
GAS_LIMIT,
None,
Code::Upload(wasm),
vec![],
vec![],
false,
)
.result
.unwrap()
.account_id;
// Check that the BOB contract has been instantiated.
get_contract(&addr);
let total_balance = <Test as Config>::Currency::total_balance(&addr);
assert_err!(
<<Test as Config>::Currency as Currency<AccountId32>>::transfer(
&addr,
&ALICE,
total_balance,
ExistenceRequirement::AllowDeath,
),
pallet_balances::Error::<Test>::KeepAlive,
);
assert_eq!(<Test as Config>::Currency::total_balance(&addr), total_balance);
});
}
#[test]
fn cannot_self_destruct_through_draning() {
let (wasm, _code_hash) = compile_module::<Test>("drain").unwrap();
@@ -1023,7 +1086,7 @@ fn cannot_self_destruct_through_draning() {
get_contract(&addr);
// Call BOB which makes it send all funds to the zero address
// The contract code asserts that the transfer was successful
// The contract code asserts that the transfer fails with the correct error code
assert_ok!(Contracts::call(
RuntimeOrigin::signed(ALICE),
addr.clone(),
@@ -1036,7 +1099,7 @@ fn cannot_self_destruct_through_draning() {
// Make sure the account wasn't remove by sending all free balance away.
assert_eq!(
<Test as Config>::Currency::total_balance(&addr),
<Test as Config>::Currency::minimum_balance(),
1_000 + <Test as Config>::Currency::minimum_balance(),
);
});
}
@@ -1093,96 +1156,10 @@ 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::total_balance(get_contract(&addr).deposit_account()),
get_contract(&addr).total_deposit(),
);
assert_eq!(get_contract(&addr).extra_deposit(), 2,);
});
}
#[test]
fn cannot_self_destruct_by_refund_after_slash() {
let (wasm, _code_hash) = compile_module::<Test>("store").unwrap();
ExtBuilder::default().existential_deposit(500).build().execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
let min_balance = <Test as Config>::Currency::minimum_balance();
let addr = Contracts::bare_instantiate(
ALICE,
0,
GAS_LIMIT,
None,
Code::Upload(wasm),
vec![],
vec![],
false,
)
.result
.unwrap()
.account_id;
// create 100 more reserved balance
assert_ok!(Contracts::call(
RuntimeOrigin::signed(ALICE),
addr.clone(),
0,
GAS_LIMIT,
None,
98u32.encode(),
));
// Drop previous events
initialize_block(2);
// slash parts of the 100 so that the next refund ould remove the account
// because it the value it stored for `storage_deposit` becomes out of sync
let _ = <Test as Config>::Currency::slash(&addr, 90);
assert_eq!(<Test as Config>::Currency::total_balance(&addr), min_balance + 10);
// trigger a refund of 50 which would bring the contract below min when actually refunded
assert_ok!(Contracts::call(
RuntimeOrigin::signed(ALICE),
addr.clone(),
0,
GAS_LIMIT,
None,
48u32.encode(),
));
// Make sure the account kept the minimum balance and was not destroyed
assert_eq!(<Test as Config>::Currency::total_balance(&addr), min_balance);
assert_eq!(
System::events(),
vec![
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::Slashed {
who: addr.clone(),
amount: 90,
}),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Contracts(crate::Event::Called {
caller: ALICE,
contract: addr.clone(),
}),
topics: vec![hash(&ALICE), hash(&addr)],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::ReserveRepatriated {
from: addr.clone(),
to: ALICE,
amount: 10,
destination_status: BalanceStatus::Free,
}),
topics: vec![],
},
]
);
assert_eq!(get_contract(&addr).extra_deposit(), 2);
});
}
@@ -1252,7 +1229,7 @@ fn self_destruct_works() {
.account_id;
// Check that the BOB contract has been instantiated.
get_contract(&addr);
let contract = get_contract(&addr);
// Drop all previous events
initialize_block(2);
@@ -1271,17 +1248,25 @@ fn self_destruct_works() {
assert_eq!(Balances::total_balance(&addr), 0);
// check that the beneficiary (django) got remaining balance
assert_eq!(Balances::free_balance(DJANGO), 1_000_000 + 100_000);
let ed = <Test as Config>::Currency::minimum_balance();
assert_eq!(Balances::free_balance(DJANGO), 1_000_000 + 100_000 + ed);
pretty_assertions::assert_eq!(
System::events(),
vec![
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::System(frame_system::Event::KilledAccount {
account: addr.clone()
}),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
from: addr.clone(),
to: DJANGO,
amount: 100_000,
amount: 100_000 + ed,
}),
topics: vec![],
},
@@ -1304,17 +1289,16 @@ fn self_destruct_works() {
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::System(frame_system::Event::KilledAccount {
account: addr.clone()
account: contract.deposit_account().deref().clone(),
}),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::ReserveRepatriated {
from: addr.clone(),
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
from: contract.deposit_account().deref().clone(),
to: ALICE,
amount: 1_000,
destination_status: BalanceStatus::Free,
}),
topics: vec![],
},
@@ -2055,7 +2039,7 @@ fn lazy_removal_on_full_queue_works_on_initialize() {
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
// Fill the deletion queue with dummy values, so that on_initialize attempts
// to clear the queue
Storage::<Test>::fill_queue_with_dummies();
ContractInfo::<Test>::fill_queue_with_dummies();
let queue_len_initial = <DeletionQueue<Test>>::decode_len().unwrap_or(0);
@@ -2132,7 +2116,7 @@ fn lazy_removal_partial_remove_works() {
// We create a contract with some extra keys above the weight limit
let extra_keys = 7u32;
let weight_limit = Weight::from_ref_time(5_000_000_000);
let (_, max_keys) = Storage::<Test>::deletion_budget(1, weight_limit);
let (_, max_keys) = ContractInfo::<Test>::deletion_budget(1, weight_limit);
let vals: Vec<_> = (0..max_keys + extra_keys)
.map(|i| (blake2_256(&i.encode()), (i as u32), (i as u32).encode()))
.collect();
@@ -2161,14 +2145,7 @@ fn lazy_removal_partial_remove_works() {
// Put value into the contracts child trie
for val in &vals {
Storage::<Test>::write(
&info.trie_id,
&val.0 as &FixSizedKey,
Some(val.2.clone()),
None,
false,
)
.unwrap();
info.write(&val.0 as &FixSizedKey, Some(val.2.clone()), None, false).unwrap();
}
<ContractInfoOf<Test>>::insert(&addr, info.clone());
@@ -2201,7 +2178,7 @@ fn lazy_removal_partial_remove_works() {
ext.execute_with(|| {
// Run the lazy removal
let weight_used = Storage::<Test>::process_deletion_queue_batch(weight_limit);
let weight_used = ContractInfo::<Test>::process_deletion_queue_batch(weight_limit);
// Weight should be exhausted because we could not even delete all keys
assert_eq!(weight_used, weight_limit);
@@ -2235,7 +2212,7 @@ fn lazy_removal_does_no_run_on_full_queue_and_full_block() {
// Fill the deletion queue with dummy values, so that on_initialize attempts
// to clear the queue
Storage::<Test>::fill_queue_with_dummies();
ContractInfo::<Test>::fill_queue_with_dummies();
// Check that on_initialize() tries to perform lazy removal but removes nothing
// as no more weight is left for that.
@@ -2344,7 +2321,7 @@ fn lazy_removal_does_not_use_all_weight() {
.account_id;
let info = get_contract(&addr);
let (weight_per_key, max_keys) = Storage::<Test>::deletion_budget(1, weight_limit);
let (weight_per_key, max_keys) = ContractInfo::<Test>::deletion_budget(1, weight_limit);
// We create a contract with one less storage item than we can remove within the limit
let vals: Vec<_> = (0..max_keys - 1)
@@ -2353,14 +2330,7 @@ fn lazy_removal_does_not_use_all_weight() {
// Put value into the contracts child trie
for val in &vals {
Storage::<Test>::write(
&info.trie_id,
&val.0 as &FixSizedKey,
Some(val.2.clone()),
None,
false,
)
.unwrap();
info.write(&val.0 as &FixSizedKey, Some(val.2.clone()), None, false).unwrap();
}
<ContractInfoOf<Test>>::insert(&addr, info.clone());
@@ -2393,7 +2363,7 @@ fn lazy_removal_does_not_use_all_weight() {
ext.execute_with(|| {
// Run the lazy removal
let weight_used = Storage::<Test>::process_deletion_queue_batch(weight_limit);
let weight_used = ContractInfo::<Test>::process_deletion_queue_batch(weight_limit);
// We have one less key in our trie than our weight limit suffices for
assert_eq!(weight_used, weight_limit - weight_per_key);
@@ -2427,7 +2397,7 @@ fn deletion_queue_full() {
.account_id;
// fill the deletion queue up until its limit
Storage::<Test>::fill_queue_with_dummies();
ContractInfo::<Test>::fill_queue_with_dummies();
// Terminate the contract should fail
assert_err_ignore_postinfo!(
@@ -3196,14 +3166,12 @@ fn instantiate_with_zero_balance_works() {
.account_id;
// Check that the BOB contract has been instantiated.
get_contract(&addr);
let contract = get_contract(&addr);
let deposit_account = contract.deposit_account().deref();
// Make sure the account exists even though no free balance was send
assert_eq!(<Test as Config>::Currency::free_balance(&addr), 0,);
assert_eq!(
<Test as Config>::Currency::total_balance(&addr),
<Test as Config>::Currency::minimum_balance(),
);
assert_eq!(<Test as Config>::Currency::free_balance(&addr), min_balance);
assert_eq!(<Test as Config>::Currency::total_balance(&addr), min_balance,);
assert_eq!(
System::events(),
@@ -3211,7 +3179,31 @@ fn instantiate_with_zero_balance_works() {
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::System(frame_system::Event::NewAccount {
account: addr.clone()
account: deposit_account.clone(),
}),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::Endowed {
account: deposit_account.clone(),
free_balance: min_balance,
}),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
from: ALICE,
to: deposit_account.clone(),
amount: min_balance,
}),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::System(frame_system::Event::NewAccount {
account: addr.clone(),
}),
topics: vec![],
},
@@ -3232,14 +3224,6 @@ fn instantiate_with_zero_balance_works() {
}),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::Reserved {
who: addr.clone(),
amount: min_balance,
}),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::Reserved {
@@ -3292,18 +3276,40 @@ fn instantiate_with_below_existential_deposit_works() {
.account_id;
// Check that the BOB contract has been instantiated.
get_contract(&addr);
let contract = get_contract(&addr);
let deposit_account = contract.deposit_account().deref();
// Make sure the account exists even though no free balance was send
assert_eq!(<Test as Config>::Currency::free_balance(&addr), 50,);
assert_eq!(
<Test as Config>::Currency::total_balance(&addr),
<Test as Config>::Currency::minimum_balance() + 50,
);
// Make sure the account exists even though not enough free balance was send
assert_eq!(<Test as Config>::Currency::free_balance(&addr), min_balance + 50);
assert_eq!(<Test as Config>::Currency::total_balance(&addr), min_balance + 50);
assert_eq!(
System::events(),
vec![
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::System(frame_system::Event::NewAccount {
account: deposit_account.clone(),
}),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::Endowed {
account: deposit_account.clone(),
free_balance: min_balance,
}),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
from: ALICE,
to: deposit_account.clone(),
amount: min_balance,
}),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::System(frame_system::Event::NewAccount {
@@ -3328,14 +3334,6 @@ fn instantiate_with_below_existential_deposit_works() {
}),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::Reserved {
who: addr.clone(),
amount: min_balance,
}),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
@@ -3436,6 +3434,9 @@ fn storage_deposit_works() {
deposit -= refunded0;
assert_eq!(get_contract(&addr).total_deposit(), deposit);
let contract = get_contract(&addr);
let deposit_account = contract.deposit_account().deref();
assert_eq!(
System::events(),
vec![
@@ -3460,15 +3461,7 @@ fn storage_deposit_works() {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
from: ALICE,
to: addr.clone(),
amount: charged0,
}),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::Reserved {
who: addr.clone(),
to: deposit_account.clone(),
amount: charged0,
}),
topics: vec![],
@@ -3485,15 +3478,7 @@ fn storage_deposit_works() {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
from: ALICE,
to: addr.clone(),
amount: charged1,
}),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::Reserved {
who: addr.clone(),
to: deposit_account.clone(),
amount: charged1,
}),
topics: vec![],
@@ -3508,11 +3493,10 @@ fn storage_deposit_works() {
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::ReserveRepatriated {
from: addr.clone(),
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
from: deposit_account.clone(),
to: ALICE,
amount: refunded0,
destination_status: BalanceStatus::Free,
}),
topics: vec![],
},
@@ -3610,11 +3594,10 @@ fn set_code_extrinsic() {
}
#[test]
fn call_after_killed_account_needs_funding() {
fn slash_cannot_kill_account() {
let (wasm, _code_hash) = compile_module::<Test>("dummy").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();
let addr = Contracts::bare_instantiate(
ALICE,
@@ -3633,107 +3616,24 @@ fn call_after_killed_account_needs_funding() {
// Drop previous events
initialize_block(2);
// Destroy the account of the contract by slashing.
// Slashing can actually happen if the contract takes part in staking.
// It is a corner case and we accept the destruction of the account.
// Try to destroy the account of the contract by slashing.
// The account does not get destroyed because of the consumer reference.
// Slashing can for example happen if the contract takes part in staking.
let _ = <Test as Config>::Currency::slash(
&addr,
<Test as Config>::Currency::total_balance(&addr),
);
// Sending below the minimum balance will fail the call because it needs to create the
// account in order to send balance there.
assert_err_ignore_postinfo!(
Contracts::call(
RuntimeOrigin::signed(ALICE),
addr.clone(),
min_balance - 1,
GAS_LIMIT,
None,
vec![],
),
<Error<Test>>::TransferFailed
);
// Sending zero should work as it does not do a transfer
assert_ok!(Contracts::call(
RuntimeOrigin::signed(ALICE),
addr.clone(),
0,
GAS_LIMIT,
None,
vec![],
));
// Sending minimum balance should work
assert_ok!(Contracts::call(
RuntimeOrigin::signed(ALICE),
addr.clone(),
min_balance,
GAS_LIMIT,
None,
vec![],
));
assert_eq!(
System::events(),
vec![
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::System(frame_system::Event::KilledAccount {
account: addr.clone()
}),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::Slashed {
who: addr.clone(),
amount: min_balance + 700
}),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Contracts(crate::Event::Called {
caller: ALICE,
contract: addr.clone(),
}),
topics: vec![hash(&ALICE), hash(&addr)],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::System(frame_system::Event::NewAccount {
account: addr.clone()
}),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::Endowed {
account: addr.clone(),
free_balance: min_balance
}),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::Transfer {
from: ALICE,
to: addr.clone(),
amount: min_balance
}),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Contracts(crate::Event::Called {
caller: ALICE,
contract: addr.clone(),
}),
topics: vec![hash(&ALICE), hash(&addr)],
},
]
vec![EventRecord {
phase: Phase::Initialization,
event: RuntimeEvent::Balances(pallet_balances::Event::Slashed {
who: addr.clone(),
amount: 700, // slash didn't remove the minimum balance
}),
topics: vec![],
},]
);
});
}