seal: Prevent contracts from going below subsistence (#6623)

* seal: Do not allow transfers to bring total balance below subsistence deposit

This also reworks the rent system to take the total balance into account
when evaluating whether the account is above the subsistence deposit.

* Fix nits from review

* Fix typo

* Do not enforce subsistence when called from EOA

* Rename CallOrigin to TransactorKind

* Add debug asserts to check the invariants of a plain account transactor

* Fix typo

Co-authored-by: Sergei Shulepov <sergei@parity.io>

Co-authored-by: Sergei Shulepov <sergei@parity.io>
This commit is contained in:
Alexander Theißen
2020-07-10 14:45:31 +02:00
committed by GitHub
parent 64114267b2
commit efc69d8219
5 changed files with 116 additions and 47 deletions
+44 -9
View File
@@ -16,17 +16,15 @@
use super::{CodeHash, Config, ContractAddressFor, Event, RawEvent, Trait,
TrieId, BalanceOf, ContractInfo, TrieIdGenerator};
use crate::gas::{Gas, GasMeter, Token};
use crate::rent;
use crate::storage;
use crate::{gas::{Gas, GasMeter, Token}, rent, storage, Error, ContractInfoOf};
use bitflags::bitflags;
use sp_std::prelude::*;
use sp_runtime::traits::{Bounded, Zero, Convert};
use sp_runtime::traits::{Bounded, Zero, Convert, Saturating};
use frame_support::{
dispatch::DispatchError,
traits::{ExistenceRequirement, Currency, Time, Randomness},
weights::Weight,
ensure, StorageMap,
};
pub type AccountIdOf<T> = <T as frame_system::Trait>::AccountId;
@@ -46,6 +44,15 @@ bitflags! {
}
}
/// Describes whether we deal with a contract or a plain account.
pub enum TransactorKind {
/// Transaction was initiated from a plain account. That can be either be through a
/// signed transaction or through RPC.
PlainAccount,
/// The call was initiated by a contract account.
Contract,
}
/// Output of a contract call or instantiation which ran to completion.
#[cfg_attr(test, derive(PartialEq, Eq, Debug))]
pub struct ExecReturnValue {
@@ -320,6 +327,7 @@ where
Err("contract has been evicted")?
};
let transactor_kind = self.transactor_kind();
let caller = self.self_account.clone();
let dest_trie_id = contract_info.and_then(|i| i.as_alive().map(|i| i.trie_id.clone()));
@@ -328,6 +336,7 @@ where
transfer(
gas_meter,
TransferCause::Call,
transactor_kind,
&caller,
&dest,
value,
@@ -372,6 +381,7 @@ where
Err("not enough gas to pay base instantiate fee")?
}
let transactor_kind = self.transactor_kind();
let caller = self.self_account.clone();
let dest = T::DetermineContractAddress::contract_address_for(
code_hash,
@@ -399,6 +409,7 @@ where
transfer(
gas_meter,
TransferCause::Instantiate,
transactor_kind,
&caller,
&dest,
endowment,
@@ -466,6 +477,17 @@ where
&self.self_account == account ||
self.caller.map_or(false, |caller| caller.is_live(account))
}
fn transactor_kind(&self) -> TransactorKind {
if self.depth == 0 {
debug_assert!(self.self_trie_id.is_none());
debug_assert!(self.caller.is_none());
debug_assert!(ContractInfoOf::<T>::get(&self.self_account).is_none());
TransactorKind::PlainAccount
} else {
TransactorKind::Contract
}
}
}
#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
@@ -519,6 +541,7 @@ enum TransferCause {
fn transfer<'a, T: Trait, V: Vm<T>, L: Loader<T>>(
gas_meter: &mut GasMeter<T>,
cause: TransferCause,
origin: TransactorKind,
transactor: &T::AccountId,
dest: &T::AccountId,
value: BalanceOf<T>,
@@ -526,6 +549,7 @@ fn transfer<'a, T: Trait, V: Vm<T>, L: Loader<T>>(
) -> Result<(), DispatchError> {
use self::TransferCause::*;
use self::TransferFeeKind::*;
use self::TransactorKind::*;
let token = {
let kind: TransferFeeKind = match cause {
@@ -545,10 +569,19 @@ fn transfer<'a, T: Trait, V: Vm<T>, L: Loader<T>>(
Err("not enough gas to pay transfer fee")?
}
// Only ext_terminate is allowed to bring the sender below the existential deposit
let existence_requirement = match cause {
Terminate => ExistenceRequirement::AllowDeath,
_ => ExistenceRequirement::KeepAlive,
// Only ext_terminate is allowed to bring the sender below the subsistence
// threshold or even existential deposit.
let existence_requirement = match (cause, origin) {
(Terminate, _) => ExistenceRequirement::AllowDeath,
(_, Contract) => {
ensure!(
T::Currency::total_balance(transactor).saturating_sub(value) >=
ctx.config.subsistence_threshold(),
Error::<T>::InsufficientBalance,
);
ExistenceRequirement::KeepAlive
},
(_, PlainAccount) => ExistenceRequirement::KeepAlive,
};
T::Currency::transfer(transactor, dest, value, existence_requirement)?;
@@ -630,6 +663,7 @@ where
transfer(
gas_meter,
TransferCause::Call,
TransactorKind::Contract,
&self.ctx.self_account.clone(),
&to,
value,
@@ -654,6 +688,7 @@ where
transfer(
gas_meter,
TransferCause::Terminate,
TransactorKind::Contract,
&self_id,
beneficiary,
value,