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
+58 -39
View File
@@ -17,9 +17,9 @@
use crate::{
gas::GasMeter,
storage::{self, Storage, WriteOutcome},
storage::{self, DepositAccount, WriteOutcome},
BalanceOf, CodeHash, Config, ContractInfo, ContractInfoOf, DebugBufferVec, Determinism, Error,
Event, Nonce, Pallet as Contracts, Schedule,
Event, Nonce, Pallet as Contracts, Schedule, System,
};
use frame_support::{
crypto::ecdsa::ECDSAExt,
@@ -492,7 +492,7 @@ enum CachedContract<T: Config> {
///
/// In this case a reload is neither allowed nor possible. Please note that recursive
/// calls cannot remove a contract as this is checked and denied.
Terminated,
Terminated(DepositAccount<T>),
}
impl<T: Config> CachedContract<T> {
@@ -513,6 +513,15 @@ impl<T: Config> CachedContract<T> {
None
}
}
/// Returns `Some` iff the contract is not `Cached::Invalidated`.
fn deposit_account(&self) -> Option<&DepositAccount<T>> {
match self {
CachedContract::Cached(contract) => Some(contract.deposit_account()),
CachedContract::Terminated(deposit_account) => Some(&deposit_account),
CachedContract::Invalidated => None,
}
}
}
impl<T: Config> Frame<T> {
@@ -591,7 +600,9 @@ impl<T: Config> CachedContract<T> {
/// Terminate and return the contract info.
fn terminate(&mut self, account_id: &T::AccountId) -> ContractInfo<T> {
self.load(account_id);
get_cached_or_panic_after_load!(mem::replace(self, Self::Terminated))
let contract = get_cached_or_panic_after_load!(self);
let deposit_account = contract.deposit_account().clone();
get_cached_or_panic_after_load!(mem::replace(self, Self::Terminated(deposit_account)))
}
}
@@ -751,9 +762,7 @@ where
input_data,
salt,
);
let trie_id = Storage::<T>::generate_trie_id(&account_id, nonce);
let contract =
Storage::<T>::new_contract(&account_id, trie_id, *executable.code_hash())?;
let contract = ContractInfo::new(&account_id, nonce, *executable.code_hash())?;
(
account_id,
contract,
@@ -873,7 +882,7 @@ where
match (entry_point, delegated_code_hash) {
(ExportedFunction::Constructor, _) => {
// It is not allowed to terminate a contract inside its constructor.
if matches!(frame.contract_info, CachedContract::Terminated) {
if matches!(frame.contract_info, CachedContract::Terminated(_)) {
return Err(Error::<T>::TerminatedInConstructor.into())
}
@@ -962,11 +971,21 @@ where
// Record the storage meter changes of the nested call into the parent meter.
// If the dropped frame's contract wasn't terminated we update the deposit counter
// in its contract info. The load is necessary to to pull it from storage in case
// in its contract info. The load is necessary to pull it from storage in case
// it was invalidated.
frame.contract_info.load(account_id);
let deposit_account = frame
.contract_info
.deposit_account()
.expect(
"Is only `None` when the info is invalidated.
We just re-loaded from storage which either makes the state `Cached` or `Terminated`.
qed",
)
.clone();
let mut contract = frame.contract_info.into_contract();
prev.nested_storage.absorb(frame.nested_storage, account_id, contract.as_mut());
prev.nested_storage
.absorb(frame.nested_storage, deposit_account, contract.as_mut());
// In case the contract wasn't terminated we need to persist changes made to it.
if let Some(contract) = contract {
@@ -1001,10 +1020,14 @@ where
if !persist {
return
}
let deposit_account = self.first_frame.contract_info.deposit_account().expect(
"Is only `None` when the info is invalidated. The first frame can't be invalidated.
qed",
).clone();
let mut contract = self.first_frame.contract_info.as_contract();
self.storage_meter.absorb(
mem::take(&mut self.first_frame.nested_storage),
&self.first_frame.account_id,
deposit_account,
contract.as_deref_mut(),
);
if let Some(contract) = contract {
@@ -1182,19 +1205,21 @@ where
}
fn terminate(&mut self, beneficiary: &AccountIdOf<Self::T>) -> Result<(), DispatchError> {
use frame_support::traits::fungible::Inspect;
if self.is_recursive() {
return Err(Error::<T>::TerminatedWhileReentrant.into())
}
let frame = self.top_frame_mut();
let info = frame.terminate();
frame.nested_storage.terminate(&info);
Storage::<T>::queue_trie_for_deletion(&info)?;
<Stack<'a, T, E>>::transfer(
ExistenceRequirement::AllowDeath,
System::<T>::dec_consumers(&frame.account_id);
T::Currency::transfer(
&frame.account_id,
beneficiary,
T::Currency::free_balance(&frame.account_id),
T::Currency::reducible_balance(&frame.account_id, false),
ExistenceRequirement::AllowDeath,
)?;
info.queue_trie_for_deletion()?;
ContractInfoOf::<T>::remove(&frame.account_id);
E::remove_user(info.code_hash);
Contracts::<T>::deposit_event(
@@ -1212,19 +1237,19 @@ where
}
fn get_storage(&mut self, key: &FixSizedKey) -> Option<Vec<u8>> {
Storage::<T>::read(&self.top_frame_mut().contract_info().trie_id, key)
self.top_frame_mut().contract_info().read(key)
}
fn get_storage_transparent(&mut self, key: &VarSizedKey<T>) -> Option<Vec<u8>> {
Storage::<T>::read(&self.top_frame_mut().contract_info().trie_id, key)
self.top_frame_mut().contract_info().read(key)
}
fn get_storage_size(&mut self, key: &FixSizedKey) -> Option<u32> {
Storage::<T>::size(&self.top_frame_mut().contract_info().trie_id, key)
self.top_frame_mut().contract_info().size(key)
}
fn get_storage_size_transparent(&mut self, key: &VarSizedKey<T>) -> Option<u32> {
Storage::<T>::size(&self.top_frame_mut().contract_info().trie_id, key)
self.top_frame_mut().contract_info().size(key)
}
fn set_storage(
@@ -1234,8 +1259,7 @@ where
take_old: bool,
) -> Result<WriteOutcome, DispatchError> {
let frame = self.top_frame_mut();
Storage::<T>::write(
&frame.contract_info.get(&frame.account_id).trie_id,
frame.contract_info.get(&frame.account_id).write(
key,
value,
Some(&mut frame.nested_storage),
@@ -1250,8 +1274,7 @@ where
take_old: bool,
) -> Result<WriteOutcome, DispatchError> {
let frame = self.top_frame_mut();
Storage::<T>::write(
&frame.contract_info.get(&frame.account_id).trie_id,
frame.contract_info.get(&frame.account_id).write(
key,
value,
Some(&mut frame.nested_storage),
@@ -1438,7 +1461,6 @@ mod tests {
use crate::{
exec::ExportedFunction::*,
gas::GasMeter,
storage::Storage,
tests::{
test_utils::{get_balance, hash, place_contract, set_balance},
ExtBuilder, RuntimeCall, RuntimeEvent as MetaEvent, Test, TestFilter, ALICE, BOB,
@@ -1891,9 +1913,8 @@ mod tests {
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable =
MockExecutable::from_storage(input_data_ch, &schedule, &mut gas_meter).unwrap();
set_balance(&ALICE, min_balance * 1000);
let mut storage_meter =
storage::meter::Meter::new(&ALICE, Some(min_balance * 100), min_balance).unwrap();
set_balance(&ALICE, min_balance * 10_000);
let mut storage_meter = storage::meter::Meter::new(&ALICE, None, min_balance).unwrap();
let result = MockStack::run_instantiate(
ALICE,
@@ -2231,7 +2252,7 @@ mod tests {
// Check that the newly created account has the expected code hash and
// there are instantiation event.
assert_eq!(
Storage::<Test>::code_hash(&instantiated_contract_address).unwrap(),
ContractInfo::<Test>::load_code_hash(&instantiated_contract_address).unwrap(),
dummy_ch
);
assert_eq!(
@@ -2273,7 +2294,7 @@ mod tests {
);
// Check that the account has not been created.
assert!(Storage::<Test>::code_hash(&instantiated_contract_address).is_none());
assert!(ContractInfo::<Test>::load_code_hash(&instantiated_contract_address).is_none());
assert!(events().is_empty());
});
}
@@ -2332,7 +2353,7 @@ mod tests {
// Check that the newly created account has the expected code hash and
// there are instantiation event.
assert_eq!(
Storage::<Test>::code_hash(&instantiated_contract_address).unwrap(),
ContractInfo::<Test>::load_code_hash(&instantiated_contract_address).unwrap(),
dummy_ch
);
assert_eq!(
@@ -2374,7 +2395,7 @@ mod tests {
set_balance(&ALICE, 1000);
set_balance(&BOB, 100);
place_contract(&BOB, instantiator_ch);
let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(100), 0).unwrap();
let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(200), 0).unwrap();
assert_matches!(
MockStack::run_call(
@@ -2409,8 +2430,8 @@ mod tests {
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable =
MockExecutable::from_storage(terminate_ch, &schedule, &mut gas_meter).unwrap();
set_balance(&ALICE, 1000);
let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(100), 100).unwrap();
set_balance(&ALICE, 10_000);
let mut storage_meter = storage::meter::Meter::new(&ALICE, None, 100).unwrap();
assert_eq!(
MockStack::run_instantiate(
@@ -2494,9 +2515,8 @@ mod tests {
let min_balance = <Test as Config>::Currency::minimum_balance();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable = MockExecutable::from_storage(code, &schedule, &mut gas_meter).unwrap();
set_balance(&ALICE, min_balance * 1000);
let mut storage_meter =
storage::meter::Meter::new(&ALICE, Some(min_balance * 100), min_balance).unwrap();
set_balance(&ALICE, min_balance * 10_000);
let mut storage_meter = storage::meter::Meter::new(&ALICE, None, min_balance).unwrap();
let result = MockStack::run_instantiate(
ALICE,
@@ -2902,10 +2922,9 @@ mod tests {
MockExecutable::from_storage(succ_fail_code, &schedule, &mut gas_meter).unwrap();
let succ_succ_executable =
MockExecutable::from_storage(succ_succ_code, &schedule, &mut gas_meter).unwrap();
set_balance(&ALICE, min_balance * 1000);
set_balance(&ALICE, min_balance * 10_000);
let mut storage_meter =
storage::meter::Meter::new(&ALICE, Some(min_balance * 500), min_balance * 100)
.unwrap();
storage::meter::Meter::new(&ALICE, None, min_balance * 100).unwrap();
MockStack::run_instantiate(
ALICE,