Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Commit

Permalink
Remove Contract/Tombstone deposit
Browse files Browse the repository at this point in the history
  • Loading branch information
athei committed Nov 5, 2021
1 parent 5858aef commit 042ea46
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 291 deletions.
4 changes: 2 additions & 2 deletions frame/contracts/fixtures/drain.wat
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
)
)

;; Self-destruct by sending full balance to the 0 address.
;; Try to self-destruct by sending full balance to the 0 address.
(call $assert
(i32.eq
(call $seal_transfer
Expand All @@ -42,7 +42,7 @@
(i32.const 0) ;; Pointer to the buffer with value to transfer
(i32.const 8) ;; Length of the buffer with value to transfer
)
(i32.const 4) ;; ReturnCode::BelowSubsistenceThreshold
(i32.const 5) ;; ReturnCode::TransferFailed
)
)
)
Expand Down
14 changes: 3 additions & 11 deletions frame/contracts/src/benchmarking/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ use frame_support::weights::Weight;
use frame_system::RawOrigin;
use pwasm_utils::parity_wasm::elements::{BlockType, BrTableData, Instruction, ValueType};
use sp_runtime::{
traits::{Bounded, Hash},
traits::{Bounded, Hash, Saturating},
Perbill,
};
use sp_std::{convert::TryInto, default::Default, vec, vec::Vec};
Expand Down Expand Up @@ -373,14 +373,6 @@ benchmarks! {
let origin = RawOrigin::Signed(instance.caller.clone());
}: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![])

seal_tombstone_deposit {
let r in 0 .. API_BENCHMARK_BATCHES;
let instance = Contract::<T>::new(WasmModule::getter(
"seal_tombstone_deposit", r * API_BENCHMARK_BATCH_SIZE
), vec![])?;
let origin = RawOrigin::Signed(instance.caller.clone());
}: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![])

seal_block_number {
let r in 0 .. API_BENCHMARK_BATCHES;
let instance = Contract::<T>::new(WasmModule::getter(
Expand Down Expand Up @@ -923,7 +915,7 @@ benchmarks! {
.collect::<Vec<_>>();
let account_len = accounts.get(0).map(|i| i.encode().len()).unwrap_or(0);
let account_bytes = accounts.iter().flat_map(|x| x.encode()).collect();
let value = Contracts::<T>::subsistence_threshold();
let value = T::Currency::minimum_balance();
assert!(value > 0u32.into());
let value_bytes = value.encode();
let value_len = value_bytes.len();
Expand Down Expand Up @@ -1111,7 +1103,7 @@ benchmarks! {
let origin = RawOrigin::Signed(instance.caller.clone());
}: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![])

// We assume that every instantiate sends at least the subsistence amount.
// We assume that every instantiate sends at least the minimum balance.
seal_instantiate {
let r in 0 .. API_BENCHMARK_BATCHES;
let hashes = (0..r * API_BENCHMARK_BATCH_SIZE)
Expand Down
73 changes: 32 additions & 41 deletions frame/contracts/src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,6 @@ pub trait Ext: sealing::Sealed {
/// Returns the minimum balance that is required for creating an account.
fn minimum_balance(&self) -> BalanceOf<Self::T>;

/// Returns the deposit required to instantiate a contract.
fn contract_deposit(&self) -> BalanceOf<Self::T>;

/// Returns a random number for the current block with the given subject.
fn random(&self, subject: &[u8]) -> (SeedOf<Self::T>, BlockNumberOf<Self::T>);

Expand Down Expand Up @@ -760,7 +757,7 @@ where
/// subsistence threshold (for contracts) or the existential deposit (for plain accounts)
/// results in an error.
fn transfer(
sender_is_contract: bool,
sender_is_origin: bool,
allow_death: bool,
from: &T::AccountId,
to: &T::AccountId,
Expand All @@ -770,17 +767,17 @@ where
return Ok(())
}

let existence_requirement = match (allow_death, sender_is_contract) {
let existence_requirement = match (allow_death, sender_is_origin) {
(true, _) => ExistenceRequirement::AllowDeath,
(false, true) => {
(false, false) => {
ensure!(
T::Currency::total_balance(from).saturating_sub(value) >=
Contracts::<T>::subsistence_threshold(),
Error::<T>::BelowSubsistenceThreshold,
T::Currency::free_balance(from).saturating_sub(value) >=
T::Currency::minimum_balance(),
Error::<T>::TransferFailed,
);
ExistenceRequirement::KeepAlive
},
(false, false) => ExistenceRequirement::KeepAlive,
(false, true) => ExistenceRequirement::KeepAlive,
};

T::Currency::transfer(from, to, value, existence_requirement)
Expand All @@ -793,21 +790,19 @@ where
fn initial_transfer(&self) -> DispatchResult {
let frame = self.top_frame();
let value = frame.value_transferred;
let subsistence_threshold = <Contracts<T>>::subsistence_threshold();
let min_balance = T::Currency::minimum_balance();

// If the value transferred to a new contract is less than the subsistence threshold
// we can error out early. This avoids executing the constructor in cases where
// we already know that the contract has too little balance.
if frame.entry_point == ExportedFunction::Constructor && value < subsistence_threshold {
return Err(<Error<T>>::NewContractNotFunded.into())
// New contracts must receive at least the minimum balance as endowment.
if frame.entry_point == ExportedFunction::Constructor && value < min_balance {
return Err(<Error<T>>::EndowmentTooLow.into())
}

Self::transfer(self.caller_is_origin(), false, self.caller(), &frame.account_id, value)
}

/// Wether the caller is the initiator of the call stack.
fn caller_is_origin(&self) -> bool {
!self.frames.is_empty()
self.frames.is_empty()
}

/// Reference to the current (top) frame.
Expand Down Expand Up @@ -941,7 +936,7 @@ where
let info = frame.terminate();
Storage::<T>::queue_trie_for_deletion(&info)?;
<Stack<'a, T, E>>::transfer(
true,
false,
true,
&frame.account_id,
beneficiary,
Expand All @@ -957,7 +952,7 @@ where
}

fn transfer(&mut self, to: &T::AccountId, value: BalanceOf<T>) -> DispatchResult {
Self::transfer(true, false, &self.top_frame().account_id, to, value)
Self::transfer(false, false, &self.top_frame().account_id, to, value)
}

fn get_storage(&mut self, key: &StorageKey) -> Option<Vec<u8>> {
Expand Down Expand Up @@ -997,10 +992,6 @@ where
T::Currency::minimum_balance()
}

fn contract_deposit(&self) -> BalanceOf<T> {
T::ContractDeposit::get()
}

fn deposit_event(&mut self, topics: Vec<T::Hash>, data: Vec<u8>) {
deposit_event::<Self::T>(
topics,
Expand Down Expand Up @@ -1354,7 +1345,7 @@ mod tests {
ExtBuilder::default().build().execute_with(|| {
set_balance(&origin, 0);

let result = MockStack::transfer(false, false, &origin, &dest, 100);
let result = MockStack::transfer(true, false, &origin, &dest, 100);

assert_eq!(result, Err(Error::<Test>::TransferFailed.into()));
assert_eq!(get_balance(&origin), 0);
Expand Down Expand Up @@ -1457,19 +1448,19 @@ mod tests {
// This one tests passing the input data into a contract via instantiate.
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
let subsistence = Contracts::<Test>::subsistence_threshold();
let min_balance = <Test as Config>::Currency::minimum_balance();
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, subsistence * 10);
set_balance(&ALICE, min_balance * 10);

let result = MockStack::run_instantiate(
ALICE,
executable,
&mut gas_meter,
&schedule,
subsistence * 3,
min_balance * 3,
vec![1, 2, 3, 4],
&[],
None,
Expand Down Expand Up @@ -1720,7 +1711,7 @@ mod tests {
.instantiate(
0,
dummy_ch,
Contracts::<Test>::subsistence_threshold() * 3,
<Test as Config>::Currency::minimum_balance() * 3,
vec![],
&[48, 49, 50],
)
Expand All @@ -1733,7 +1724,7 @@ mod tests {

ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
set_balance(&ALICE, Contracts::<Test>::subsistence_threshold() * 100);
set_balance(&ALICE, <Test as Config>::Currency::minimum_balance() * 100);
place_contract(&BOB, instantiator_ch);

assert_matches!(
Expand Down Expand Up @@ -1776,7 +1767,7 @@ mod tests {
ctx.ext.instantiate(
0,
dummy_ch,
Contracts::<Test>::subsistence_threshold(),
<Test as Config>::Currency::minimum_balance(),
vec![],
&[],
),
Expand Down Expand Up @@ -1906,18 +1897,18 @@ mod tests {
// This one tests passing the input data into a contract via instantiate.
ExtBuilder::default().build().execute_with(|| {
let schedule = <Test as Config>::Schedule::get();
let subsistence = Contracts::<Test>::subsistence_threshold();
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, subsistence * 10);
set_balance(&ALICE, min_balance * 10);

let result = MockStack::run_instantiate(
ALICE,
executable,
&mut gas_meter,
&schedule,
subsistence * 3,
min_balance * 3,
vec![],
&[],
None,
Expand All @@ -1937,10 +1928,10 @@ mod tests {
let mut debug_buffer = Vec::new();

ExtBuilder::default().build().execute_with(|| {
let subsistence = Contracts::<Test>::subsistence_threshold();
let min_balance = <Test as Config>::Currency::minimum_balance();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, subsistence * 10);
set_balance(&ALICE, min_balance * 10);
place_contract(&BOB, code_hash);
MockStack::run_call(
ALICE,
Expand Down Expand Up @@ -1968,10 +1959,10 @@ mod tests {
let mut debug_buffer = Vec::new();

ExtBuilder::default().build().execute_with(|| {
let subsistence = Contracts::<Test>::subsistence_threshold();
let min_balance = <Test as Config>::Currency::minimum_balance();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, subsistence * 10);
set_balance(&ALICE, min_balance * 10);
place_contract(&BOB, code_hash);
let result = MockStack::run_call(
ALICE,
Expand Down Expand Up @@ -2078,10 +2069,10 @@ mod tests {
});

ExtBuilder::default().build().execute_with(|| {
let subsistence = Contracts::<Test>::subsistence_threshold();
let min_balance = <Test as Config>::Currency::minimum_balance();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, subsistence * 10);
set_balance(&ALICE, min_balance * 10);
place_contract(&BOB, code_hash);
System::reset_events();
MockStack::run_call(ALICE, BOB, &mut gas_meter, &schedule, 0, vec![], None).unwrap();
Expand Down Expand Up @@ -2135,10 +2126,10 @@ mod tests {
});

ExtBuilder::default().build().execute_with(|| {
let subsistence = Contracts::<Test>::subsistence_threshold();
let min_balance = <Test as Config>::Currency::minimum_balance();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, subsistence * 10);
set_balance(&ALICE, min_balance * 10);
place_contract(&BOB, code_hash);
System::reset_events();
MockStack::run_call(ALICE, BOB, &mut gas_meter, &schedule, 0, vec![], None).unwrap();
Expand Down
63 changes: 15 additions & 48 deletions frame/contracts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ use pallet_contracts_primitives::{
GetStorageResult, InstantiateReturnValue,
};
use sp_core::{crypto::UncheckedFrom, Bytes};
use sp_runtime::traits::{Convert, Hash, Saturating, StaticLookup};
use sp_runtime::traits::{Convert, Hash, StaticLookup};
use sp_std::prelude::*;

type CodeHash<T> = <T as frame_system::Config>::Hash;
Expand Down Expand Up @@ -165,12 +165,6 @@ pub mod pallet {
/// This is applied in **addition** to [`frame_system::Config::BaseCallFilter`].
/// It is recommended to treat this as a whitelist.
///
/// # Subsistence Threshold
///
/// The runtime **must** make sure that any allowed dispatchable makes sure that the
/// `total_balance` of the contract stays above [`Pallet::subsistence_threshold()`].
/// Otherwise users could clutter the storage with contracts.
///
/// # Stability
///
/// The runtime **must** make sure that all dispatchables that are callable by
Expand Down Expand Up @@ -201,13 +195,6 @@ pub mod pallet {
#[pallet::constant]
type Schedule: Get<Schedule<Self>>;

/// The deposit that must be placed into the contract's account to instantiate it.
/// This is in **addition** to the [`pallet_balances::Pallet::ExistenialDeposit`].
/// The minimum balance for a contract's account can be queried using
/// [`Pallet::subsistence_threshold`].
#[pallet::constant]
type ContractDeposit: Get<BalanceOf<Self>>;

/// The type of the call stack determines the maximum nesting depth of contract calls.
///
/// The allowed depth is `CallStack::size() + 1`.
Expand Down Expand Up @@ -417,16 +404,20 @@ pub mod pallet {
OutOfGas,
/// The output buffer supplied to a contract API call was too small.
OutputBufferTooSmall,
/// Performing the requested transfer would have brought the contract below
/// the subsistence threshold. No transfer is allowed to do this. Use `seal_terminate`
/// to recover a deposit.
BelowSubsistenceThreshold,
/// The newly created contract is below the subsistence threshold after executing
/// its contructor. No contracts are allowed to exist below that threshold.
NewContractNotFunded,
/// Performing the requested transfer failed for a reason originating in the
/// chosen currency implementation of the runtime. Most probably the balance is
/// too low or locks are placed on it.
/// When creating a new contract at least the minimum balance must be provided
/// as endowment. If that is not the case this error is returned.
EndowmentTooLow,
/// Performing the requested transfer failed. Most probably the transfer would have
/// brought the contract's account below the minimum balance. Other possible reasons
/// are balance locks or reservations on the contract's account.
///
/// # Note
///
/// Any transfer must leave the minimum balance as **free** balance in the contract.
/// This is different from a usual keep alive transfer where the reserved balance
/// also counts into the minimum balance. This is enforced because otherwise a refund
/// of a storage deposit could remove a contract's account as it was only kept alive
/// by this deposit.
TransferFailed,
/// Performing a call was denied because the calling depth reached the limit
/// of what is specified in the schedule.
Expand Down Expand Up @@ -636,30 +627,6 @@ where
UncheckedFrom::unchecked_from(T::Hashing::hash(&buf))
}

/// Subsistence threshold is the extension of the minimum balance (aka existential deposit)
/// by the contract deposit. It is the minimum balance any contract must hold.
///
/// Any contract initiated balance transfer mechanism cannot make the balance lower
/// than the subsistence threshold. The only way to recover the balance is to remove
/// contract using `seal_terminate`.
pub fn subsistence_threshold() -> BalanceOf<T> {
T::Currency::minimum_balance().saturating_add(T::ContractDeposit::get())
}

/// The in-memory size in bytes of the data structure associated with each contract.
///
/// The data structure is also put into storage for each contract. The in-storage size
/// is never larger than the in-memory representation and usually smaller due to compact
/// encoding and lack of padding.
///
/// # Note
///
/// This returns the in-memory size because the in-storage size (SCALE encoded) cannot
/// be efficiently determined. Treat this as an upper bound of the in-storage size.
pub fn contract_info_size() -> u32 {
sp_std::mem::size_of::<ContractInfo<T>>() as u32
}

/// Store code for benchmarks which does not check nor instrument the code.
#[cfg(feature = "runtime-benchmarks")]
fn store_code_raw(code: Vec<u8>) -> frame_support::dispatch::DispatchResult {
Expand Down
Loading

0 comments on commit 042ea46

Please sign in to comment.