contracts: Collect rent for the first block during deployment (#7847)

* Pay first rent during instantiation

* Fix and add new tests

* Do not increment trie id counter on failure
This commit is contained in:
Alexander Theißen
2021-01-11 11:16:50 +01:00
committed by GitHub
parent 6d8d9ac354
commit ca0a636b15
4 changed files with 219 additions and 148 deletions
@@ -336,7 +336,7 @@ benchmarks! {
let s in 0 .. code::max_pages::<T>() * 64;
let data = vec![42u8; (n * 1024) as usize];
let salt = vec![42u8; (s * 1024) as usize];
let endowment = ConfigCache::<T>::subsistence_threshold_uncached();
let endowment = caller_funding::<T>() / 3u32.into();
let caller = whitelisted_caller();
T::Currency::make_free_balance_be(&caller, caller_funding::<T>());
let WasmModule { code, hash, .. } = WasmModule::<T>::dummy_with_mem();
@@ -1373,7 +1373,7 @@ benchmarks! {
let hash_len = hashes.get(0).map(|x| x.encode().len()).unwrap_or(0);
let hashes_bytes = hashes.iter().flat_map(|x| x.encode()).collect::<Vec<_>>();
let hashes_len = hashes_bytes.len();
let value = ConfigCache::<T>::subsistence_threshold_uncached();
let value = Endow::max::<T>() / (r * API_BENCHMARK_BATCH_SIZE + 2).into();
assert!(value > 0u32.into());
let value_bytes = value.encode();
let value_len = value_bytes.len();
@@ -1457,7 +1457,8 @@ benchmarks! {
}: call(origin, callee, 0u32.into(), Weight::max_value(), vec![])
verify {
for addr in &addresses {
instance.alive_info()?;
ContractInfoOf::<T>::get(&addr).and_then(|c| c.get_alive())
.ok_or_else(|| "Contract should have been instantiated")?;
}
}
@@ -1493,7 +1494,7 @@ benchmarks! {
let input_len = inputs.get(0).map(|x| x.len()).unwrap_or(0);
let input_bytes = inputs.iter().cloned().flatten().collect::<Vec<_>>();
let inputs_len = input_bytes.len();
let value = ConfigCache::<T>::subsistence_threshold_uncached();
let value = Endow::max::<T>() / (API_BENCHMARK_BATCH_SIZE + 2).into();
assert!(value > 0u32.into());
let value_bytes = value.encode();
let value_len = value_bytes.len();
+64 -53
View File
@@ -322,53 +322,62 @@ where
let caller = self.self_account.clone();
let dest = Contracts::<T>::contract_address(&caller, code_hash, salt);
// TrieId has not been generated yet and storage is empty since contract is new.
//
// Generate it now.
let dest_trie_id = Storage::<T>::generate_trie_id(&dest);
let output = frame_support::storage::with_transaction(|| {
// Generate the trie id in a new transaction to only increment the counter on success.
let dest_trie_id = Storage::<T>::generate_trie_id(&dest);
let output = self.with_nested_context(dest.clone(), dest_trie_id, |nested| {
Storage::<T>::place_contract(
&dest,
nested
.self_trie_id
.clone()
.expect("the nested context always has to have self_trie_id"),
code_hash.clone()
)?;
let output = self.with_nested_context(dest.clone(), dest_trie_id, |nested| {
Storage::<T>::place_contract(
&dest,
nested
.self_trie_id
.clone()
.expect("the nested context always has to have self_trie_id"),
code_hash.clone()
)?;
// Send funds unconditionally here. If the `endowment` is below existential_deposit
// then error will be returned here.
transfer(
TransferCause::Instantiate,
transactor_kind,
&caller,
&dest,
endowment,
nested,
)?;
// Send funds unconditionally here. If the `endowment` is below existential_deposit
// then error will be returned here.
transfer(
TransferCause::Instantiate,
transactor_kind,
&caller,
&dest,
endowment,
nested,
)?;
let executable = nested.loader.load_init(&code_hash)
.map_err(|_| Error::<T>::CodeNotFound)?;
let output = nested.vm
.execute(
&executable,
nested.new_call_context(caller.clone(), endowment),
input_data,
gas_meter,
).map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee })?;
let executable = nested.loader.load_init(&code_hash)
.map_err(|_| Error::<T>::CodeNotFound)?;
let output = nested.vm
.execute(
&executable,
nested.new_call_context(caller.clone(), endowment),
input_data,
gas_meter,
).map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee })?;
// We need each contract that exists to be above the subsistence threshold
// in order to keep up the guarantuee that we always leave a tombstone behind
// with the exception of a contract that called `seal_terminate`.
if T::Currency::total_balance(&dest) < nested.config.subsistence_threshold() {
Err(Error::<T>::NewContractNotFunded)?
// Collect the rent for the first block to prevent the creation of very large
// contracts that never intended to pay for even one block.
// This also makes sure that it is above the subsistence threshold
// in order to keep up the guarantuee that we always leave a tombstone behind
// with the exception of a contract that called `seal_terminate`.
Rent::<T>::charge(&dest)?
.and_then(|c| c.get_alive())
.ok_or_else(|| Error::<T>::NewContractNotFunded)?;
// Deposit an instantiation event.
deposit_event::<T>(vec![], RawEvent::Instantiated(caller.clone(), dest.clone()));
Ok(output)
});
use frame_support::storage::TransactionOutcome::*;
match output {
Ok(_) => Commit(output),
Err(_) => Rollback(output),
}
// Deposit an instantiation event.
deposit_event::<T>(vec![], RawEvent::Instantiated(caller.clone(), dest.clone()));
Ok(output)
})?;
Ok((dest, output))
@@ -908,7 +917,7 @@ mod tests {
let mut ctx = ExecutionContext::top_level(origin.clone(), &cfg, &vm, &loader);
place_contract(&BOB, return_ch);
set_balance(&origin, 100);
set_balance(&dest, 0);
let balance = get_balance(&dest);
let output = ctx.call(
dest.clone(),
@@ -919,7 +928,9 @@ mod tests {
assert!(!output.is_success());
assert_eq!(get_balance(&origin), 100);
assert_eq!(get_balance(&dest), 0);
// the rent is still charged
assert!(get_balance(&dest) < balance);
});
}
@@ -1057,10 +1068,10 @@ mod tests {
let cfg = ConfigCache::preload();
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
set_balance(&ALICE, 100);
set_balance(&ALICE, cfg.subsistence_threshold() * 10);
let result = ctx.instantiate(
cfg.subsistence_threshold(),
cfg.subsistence_threshold() * 3,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&input_data_ch,
vec![1, 2, 3, 4],
@@ -1307,7 +1318,7 @@ mod tests {
// Instantiate a contract and save it's address in `instantiated_contract_address`.
let (address, output) = ctx.ext.instantiate(
&dummy_ch,
ConfigCache::<Test>::subsistence_threshold_uncached(),
ConfigCache::<Test>::subsistence_threshold_uncached() * 3,
ctx.gas_meter,
vec![],
&[48, 49, 50],
@@ -1321,8 +1332,7 @@ mod tests {
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
let cfg = ConfigCache::preload();
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
set_balance(&ALICE, 1000);
set_balance(&BOB, 100);
set_balance(&ALICE, cfg.subsistence_threshold() * 100);
place_contract(&BOB, instantiator_ch);
assert_matches!(
@@ -1431,19 +1441,20 @@ mod tests {
let vm = MockVm::new();
let mut loader = MockLoader::empty();
let rent_allowance_ch = loader.insert(|ctx| {
let allowance = ConfigCache::<Test>::subsistence_threshold_uncached() * 3;
assert_eq!(ctx.ext.rent_allowance(), <BalanceOf<Test>>::max_value());
ctx.ext.set_rent_allowance(10);
assert_eq!(ctx.ext.rent_allowance(), 10);
ctx.ext.set_rent_allowance(allowance);
assert_eq!(ctx.ext.rent_allowance(), allowance);
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let cfg = ConfigCache::preload();
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
set_balance(&ALICE, 100);
set_balance(&ALICE, cfg.subsistence_threshold() * 10);
let result = ctx.instantiate(
cfg.subsistence_threshold(),
cfg.subsistence_threshold() * 5,
&mut GasMeter::<Test>::new(GAS_LIMIT),
&rent_allowance_ch,
vec![],
+6 -2
View File
@@ -27,7 +27,7 @@ use codec::{Encode, Decode};
use sp_std::prelude::*;
use sp_std::marker::PhantomData;
use sp_io::hashing::blake2_256;
use sp_runtime::traits::Bounded;
use sp_runtime::traits::{Bounded, Saturating};
use sp_core::crypto::UncheckedFrom;
use frame_support::{
dispatch::DispatchResult,
@@ -182,7 +182,11 @@ where
code_hash: ch,
storage_size: 0,
trie_id,
deduct_block: <frame_system::Module<T>>::block_number(),
deduct_block:
// We want to charge rent for the first block in advance. Therefore we
// treat the contract as if it was created in the last block and then
// charge rent for it during instantation.
<frame_system::Module<T>>::block_number().saturating_sub(1u32.into()),
rent_allowance: <BalanceOf<T>>::max_value(),
pair_count: 0,
last_write: None,
+144 -89
View File
@@ -35,7 +35,7 @@ use sp_runtime::{
use sp_io::hashing::blake2_256;
use frame_support::{
assert_ok, assert_err, assert_err_ignore_postinfo, impl_outer_dispatch, impl_outer_event,
impl_outer_origin, parameter_types, StorageMap,
impl_outer_origin, parameter_types, StorageMap, assert_storage_noop,
traits::{Currency, ReservableCurrency, OnInitialize},
weights::{Weight, PostDispatchInfo, DispatchClass, constants::WEIGHT_PER_SECOND},
dispatch::DispatchErrorWithPostInfo,
@@ -74,6 +74,7 @@ impl_outer_dispatch! {
pub mod test_utils {
use super::{Test, Balances};
use crate::{
ConfigCache,
ContractInfoOf, CodeHash,
storage::Storage,
exec::{StorageKey, AccountIdOf},
@@ -90,6 +91,7 @@ pub mod test_utils {
}
pub fn place_contract(address: &AccountIdOf<Test>, code_hash: CodeHash<Test>) {
let trie_id = Storage::<Test>::generate_trie_id(address);
set_balance(address, ConfigCache::<Test>::subsistence_threshold_uncached() * 10);
Storage::<Test>::place_contract(&address, trie_id, code_hash).unwrap()
}
pub fn set_balance(who: &AccountIdOf<Test>, amount: u64) {
@@ -453,14 +455,14 @@ fn instantiate_and_call_and_deposit_event() {
.build()
.execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
let subsistence = super::ConfigCache::<Test>::subsistence_threshold_uncached();
let subsistence = ConfigCache::<Test>::subsistence_threshold_uncached();
assert_ok!(Contracts::put_code(Origin::signed(ALICE), wasm));
// Check at the end to get hash on error easily
let creation = Contracts::instantiate(
Origin::signed(ALICE),
subsistence,
subsistence * 3,
GAS_LIMIT,
code_hash.into(),
vec![],
@@ -494,14 +496,14 @@ fn instantiate_and_call_and_deposit_event() {
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::balances(
pallet_balances::RawEvent::Endowed(addr.clone(), subsistence)
pallet_balances::RawEvent::Endowed(addr.clone(), subsistence * 3)
),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::balances(
pallet_balances::RawEvent::Transfer(ALICE, addr.clone(), subsistence)
pallet_balances::RawEvent::Transfer(ALICE, addr.clone(), subsistence * 3)
),
topics: vec![],
},
@@ -545,12 +547,19 @@ fn deposit_event_max_value_limit() {
));
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
// The instantation deducted the rent for one block immediatly
let first_rent = <Test as Config>::RentFraction::get()
// base_deposit - free_balance
.mul_ceil(80_000 - 30_000)
// blocks to rent
* 1;
// Check creation
let bob_contract = ContractInfoOf::<Test>::get(addr.clone())
.unwrap()
.get_alive()
.unwrap();
assert_eq!(bob_contract.rent_allowance, <BalanceOf<Test>>::max_value());
assert_eq!(bob_contract.rent_allowance, <BalanceOf<Test>>::max_value() - first_rent);
// Call contract with allowed storage value.
assert_ok!(Contracts::call(
@@ -617,6 +626,7 @@ mod call {
use super::{AccountIdOf, Test};
pub fn set_storage_4_byte() -> Vec<u8> { 0u32.to_le_bytes().to_vec() }
pub fn remove_storage_4_byte() -> Vec<u8> { 1u32.to_le_bytes().to_vec() }
#[allow(dead_code)]
pub fn transfer(to: &AccountIdOf<Test>) -> Vec<u8> {
2u32.to_le_bytes().iter().chain(AsRef::<[u8]>::as_ref(to)).cloned().collect()
}
@@ -798,10 +808,18 @@ fn deduct_blocks() {
vec![],
));
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
ContractInfoOf::<Test>::get(&addr).unwrap().get_alive().unwrap();
// Check creation
// The instantation deducted the rent for one block immediatly
let rent0 = <Test as Config>::RentFraction::get()
// base_deposit + deploy_set_storage (4 bytes in 1 item) - free_balance
.mul_ceil(80_000 + 40_000 + 10_000 - 30_000)
// blocks to rent
* 1;
let bob_contract = ContractInfoOf::<Test>::get(&addr).unwrap().get_alive().unwrap();
assert_eq!(bob_contract.rent_allowance, 1_000);
assert_eq!(bob_contract.rent_allowance, 1_000 - rent0);
assert_eq!(bob_contract.deduct_block, 1);
assert_eq!(Balances::free_balance(&addr), 30_000 - rent0);
// Advance 4 blocks
initialize_block(5);
@@ -814,13 +832,13 @@ fn deduct_blocks() {
// Check result
let rent = <Test as Config>::RentFraction::get()
// base_deposit + deploy_set_storage (4 bytes in 1 item) - free_balance
.mul_ceil(80_000 + 40_000 + 10_000 - 30_000)
.mul_ceil(80_000 + 40_000 + 10_000 - (30_000 - rent0))
// blocks to rent
* 4;
let bob_contract = ContractInfoOf::<Test>::get(&addr).unwrap().get_alive().unwrap();
assert_eq!(bob_contract.rent_allowance, 1_000 - rent);
assert_eq!(bob_contract.rent_allowance, 1_000 - rent0 - rent);
assert_eq!(bob_contract.deduct_block, 5);
assert_eq!(Balances::free_balance(&addr), 30_000 - rent);
assert_eq!(Balances::free_balance(&addr), 30_000 - rent0 - rent);
// Advance 7 blocks more
initialize_block(12);
@@ -833,23 +851,22 @@ fn deduct_blocks() {
// Check result
let rent_2 = <Test as Config>::RentFraction::get()
// base_deposit + deploy_set_storage (4 bytes in 1 item) - free_balance
.mul_ceil(80_000 + 40_000 + 10_000 - (30_000 - rent))
.mul_ceil(80_000 + 40_000 + 10_000 - (30_000 - rent0 - rent))
// blocks to rent
* 7;
let bob_contract = ContractInfoOf::<Test>::get(&addr).unwrap().get_alive().unwrap();
assert_eq!(bob_contract.rent_allowance, 1_000 - rent - rent_2);
assert_eq!(bob_contract.rent_allowance, 1_000 - rent0 - rent - rent_2);
assert_eq!(bob_contract.deduct_block, 12);
assert_eq!(Balances::free_balance(&addr), 30_000 - rent - rent_2);
assert_eq!(Balances::free_balance(&addr), 30_000 - rent0 - rent - rent_2);
// Second call on same block should have no effect on rent
assert_ok!(
Contracts::call(Origin::signed(ALICE), addr.clone(), 0, GAS_LIMIT, call::null())
);
let bob_contract = ContractInfoOf::<Test>::get(&addr).unwrap().get_alive().unwrap();
assert_eq!(bob_contract.rent_allowance, 1_000 - rent - rent_2);
assert_eq!(bob_contract.rent_allowance, 1_000 - rent0 - rent - rent_2);
assert_eq!(bob_contract.deduct_block, 12);
assert_eq!(Balances::free_balance(&addr), 30_000 - rent - rent_2);
assert_eq!(Balances::free_balance(&addr), 30_000 - rent0 - rent - rent_2)
});
}
@@ -866,16 +883,16 @@ fn signed_claim_surcharge_contract_removals() {
#[test]
fn claim_surcharge_malus() {
// Test surcharge malus for inherent
claim_surcharge(4, |addr| Contracts::claim_surcharge(Origin::none(), addr, Some(ALICE)).is_ok(), true);
claim_surcharge(3, |addr| Contracts::claim_surcharge(Origin::none(), addr, Some(ALICE)).is_ok(), true);
claim_surcharge(2, |addr| Contracts::claim_surcharge(Origin::none(), addr, Some(ALICE)).is_ok(), true);
claim_surcharge(1, |addr| Contracts::claim_surcharge(Origin::none(), addr, Some(ALICE)).is_ok(), false);
claim_surcharge(27, |addr| Contracts::claim_surcharge(Origin::none(), addr, Some(ALICE)).is_ok(), true);
claim_surcharge(26, |addr| Contracts::claim_surcharge(Origin::none(), addr, Some(ALICE)).is_ok(), true);
claim_surcharge(25, |addr| Contracts::claim_surcharge(Origin::none(), addr, Some(ALICE)).is_ok(), true);
claim_surcharge(24, |addr| Contracts::claim_surcharge(Origin::none(), addr, Some(ALICE)).is_ok(), false);
// Test surcharge malus for signed
claim_surcharge(4, |addr| Contracts::claim_surcharge(Origin::signed(ALICE), addr, None).is_ok(), true);
claim_surcharge(3, |addr| Contracts::claim_surcharge(Origin::signed(ALICE), addr, None).is_ok(), false);
claim_surcharge(2, |addr| Contracts::claim_surcharge(Origin::signed(ALICE), addr, None).is_ok(), false);
claim_surcharge(1, |addr| Contracts::claim_surcharge(Origin::signed(ALICE), addr, None).is_ok(), false);
claim_surcharge(27, |addr| Contracts::claim_surcharge(Origin::signed(ALICE), addr, None).is_ok(), true);
claim_surcharge(26, |addr| Contracts::claim_surcharge(Origin::signed(ALICE), addr, None).is_ok(), false);
claim_surcharge(25, |addr| Contracts::claim_surcharge(Origin::signed(ALICE), addr, None).is_ok(), false);
claim_surcharge(24, |addr| Contracts::claim_surcharge(Origin::signed(ALICE), addr, None).is_ok(), false);
}
/// Claim surcharge with the given trigger_call at the given blocks.
@@ -892,7 +909,7 @@ fn claim_surcharge(blocks: u64, trigger_call: impl Fn(AccountIdOf<Test>) -> bool
assert_ok!(Contracts::put_code(Origin::signed(ALICE), wasm));
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
100,
30_000,
GAS_LIMIT, code_hash.into(),
<Test as pallet_balances::Config>::Balance::from(1_000u32).encode(), // rent allowance
vec![],
@@ -931,30 +948,36 @@ fn removals(trigger_call: impl Fn(AccountIdOf<Test>) -> bool) {
assert_ok!(Contracts::put_code(Origin::signed(ALICE), wasm.clone()));
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
100,
500,
GAS_LIMIT, code_hash.into(),
<Test as pallet_balances::Config>::Balance::from(1_000u32).encode(), // rent allowance
vec![],
));
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
let allowance = ContractInfoOf::<Test>::get(&addr)
.unwrap().get_alive().unwrap().rent_allowance;
let balance = Balances::free_balance(&addr);
let subsistence_threshold = 50 /*existential_deposit*/ + 16 /*tombstone_deposit*/;
let subsistence_threshold = ConfigCache::<Test>::subsistence_threshold_uncached();
// Trigger rent must have no effect
assert!(!trigger_call(addr.clone()));
assert_eq!(ContractInfoOf::<Test>::get(&addr).unwrap().get_alive().unwrap().rent_allowance, 1_000);
assert_eq!(Balances::free_balance(&addr), 100);
assert_eq!(
ContractInfoOf::<Test>::get(&addr).unwrap().get_alive().unwrap().rent_allowance,
allowance,
);
assert_eq!(Balances::free_balance(&addr), balance);
// Advance blocks
initialize_block(10);
initialize_block(27);
// Trigger rent through call
// Trigger rent through call (should remove the contract)
assert!(trigger_call(addr.clone()));
assert!(ContractInfoOf::<Test>::get(&addr).unwrap().get_tombstone().is_some());
assert_eq!(Balances::free_balance(&addr), subsistence_threshold);
// Advance blocks
initialize_block(20);
initialize_block(30);
// Trigger rent must have no effect
assert!(!trigger_call(addr.clone()));
@@ -972,13 +995,16 @@ fn removals(trigger_call: impl Fn(AccountIdOf<Test>) -> bool) {
assert_ok!(Contracts::put_code(Origin::signed(ALICE), wasm.clone()));
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
1_000,
30_000,
GAS_LIMIT,
code_hash.into(),
<Test as pallet_balances::Config>::Balance::from(100u32).encode(), // rent allowance
<Test as pallet_balances::Config>::Balance::from(1000u32).encode(), // rent allowance
vec![],
));
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
let allowance = ContractInfoOf::<Test>::get(&addr)
.unwrap().get_alive().unwrap().rent_allowance;
let balance = Balances::free_balance(&addr);
// Trigger rent must have no effect
assert!(!trigger_call(addr.clone()));
@@ -988,12 +1014,12 @@ fn removals(trigger_call: impl Fn(AccountIdOf<Test>) -> bool) {
.get_alive()
.unwrap()
.rent_allowance,
100
allowance,
);
assert_eq!(Balances::free_balance(&addr), 1_000);
assert_eq!(Balances::free_balance(&addr), balance);
// Advance blocks
initialize_block(10);
initialize_block(27);
// Trigger rent through call
assert!(trigger_call(addr.clone()));
@@ -1002,7 +1028,7 @@ fn removals(trigger_call: impl Fn(AccountIdOf<Test>) -> bool) {
.get_tombstone()
.is_some());
// Balance should be initial balance - initial rent_allowance
assert_eq!(Balances::free_balance(&addr), 900);
assert_eq!(Balances::free_balance(&addr), 29000);
// Advance blocks
initialize_block(20);
@@ -1013,7 +1039,7 @@ fn removals(trigger_call: impl Fn(AccountIdOf<Test>) -> bool) {
.unwrap()
.get_tombstone()
.is_some());
assert_eq!(Balances::free_balance(&addr), 900);
assert_eq!(Balances::free_balance(&addr), 29000);
});
// Balance reached and inferior to subsistence threshold
@@ -1023,18 +1049,20 @@ fn removals(trigger_call: impl Fn(AccountIdOf<Test>) -> bool) {
.execute_with(|| {
// Create
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
let subsistence_threshold =
Balances::minimum_balance() + <Test as Config>::TombstoneDeposit::get();
let subsistence_threshold = ConfigCache::<Test>::subsistence_threshold_uncached();
assert_ok!(Contracts::put_code(Origin::signed(ALICE), wasm.clone()));
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
50 + subsistence_threshold,
subsistence_threshold * 3,
GAS_LIMIT,
code_hash.into(),
<Test as pallet_balances::Config>::Balance::from(1_000u32).encode(), // rent allowance
vec![],
));
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
let allowance = ContractInfoOf::<Test>::get(&addr)
.unwrap().get_alive().unwrap().rent_allowance;
let balance = Balances::free_balance(&addr);
// Trigger rent must have no effect
assert!(!trigger_call(addr.clone()));
@@ -1044,32 +1072,18 @@ fn removals(trigger_call: impl Fn(AccountIdOf<Test>) -> bool) {
.get_alive()
.unwrap()
.rent_allowance,
1_000
allowance,
);
assert_eq!(
Balances::free_balance(&addr),
50 + subsistence_threshold,
balance,
);
// Transfer funds
assert_ok!(Contracts::call(
Origin::signed(ALICE),
addr.clone(),
0,
GAS_LIMIT,
call::transfer(&BOB),
));
assert_eq!(
ContractInfoOf::<Test>::get(&addr)
.unwrap()
.get_alive()
.unwrap()
.rent_allowance,
1_000
);
// Make contract have exactly the subsitence threshold
Balances::make_free_balance_be(&addr, subsistence_threshold);
assert_eq!(Balances::free_balance(&addr), subsistence_threshold);
// Advance blocks
// Advance blocks (should remove as balance is exactly subsistence)
initialize_block(10);
// Trigger rent through call
@@ -1101,7 +1115,7 @@ fn call_removed_contract() {
assert_ok!(Contracts::put_code(Origin::signed(ALICE), wasm.clone()));
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
100,
30_000,
GAS_LIMIT, code_hash.into(),
<Test as pallet_balances::Config>::Balance::from(1_000u32).encode(), // rent allowance
vec![],
@@ -1114,7 +1128,7 @@ fn call_removed_contract() {
);
// Advance blocks
initialize_block(10);
initialize_block(27);
// Calling contract should deny access because rent cannot be paid.
assert_err_ignore_postinfo!(
@@ -1157,9 +1171,16 @@ fn default_rent_allowance_on_instantiate() {
));
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
// The instantation deducted the rent for one block immediatly
let first_rent = <Test as Config>::RentFraction::get()
// base_deposit - free_balance
.mul_ceil(80_000 - 30_000)
// blocks to rent
* 1;
// Check creation
let bob_contract = ContractInfoOf::<Test>::get(&addr).unwrap().get_alive().unwrap();
assert_eq!(bob_contract.rent_allowance, <BalanceOf<Test>>::max_value());
assert_eq!(bob_contract.rent_allowance, <BalanceOf<Test>>::max_value() - first_rent);
// Advance blocks
initialize_block(5);
@@ -1239,15 +1260,15 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage:
30_000,
GAS_LIMIT,
set_rent_code_hash.into(),
<Test as pallet_balances::Config>::Balance::from(0u32).encode(),
<Test as pallet_balances::Config>::Balance::from(1_000u32).encode(),
vec![],
));
let addr_bob = Contracts::contract_address(&ALICE, &set_rent_code_hash, &[]);
// Check if `BOB` was created successfully and that the rent allowance is
// set to 0.
// Check if `BOB` was created successfully and that the rent allowance is below what
// we specified as the first rent was already collected.
let bob_contract = ContractInfoOf::<Test>::get(&addr_bob).unwrap().get_alive().unwrap();
assert_eq!(bob_contract.rent_allowance, 0);
assert!(bob_contract.rent_allowance < 5_000);
if test_different_storage {
assert_ok!(Contracts::call(
@@ -1257,10 +1278,10 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage:
);
}
// Advance 4 blocks, to the 5th.
initialize_block(5);
// Advance blocks in order to make the contract run out of money for rent.
initialize_block(27);
// Call `BOB`, which makes it pay rent. Since the rent allowance is set to 0
// Call `BOB`, which makes it pay rent. Since the rent allowance is set to 20_000
// we expect that it is no longer callable but keeps existing until someone
// calls `claim_surcharge`.
assert_err_ignore_postinfo!(
@@ -1304,8 +1325,8 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage:
// The trie is regarded as 'dirty' when it was written to in the current block.
if !test_restore_to_with_dirty_storage {
// Advance 1 block, to the 6th.
initialize_block(6);
// Advance 1 block.
initialize_block(28);
}
// Perform a call to `DJANGO`. This should either perform restoration successfully or
@@ -1452,10 +1473,7 @@ fn storage_max_value_limit() {
vec![],
));
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
// Check creation
let bob_contract = ContractInfoOf::<Test>::get(&addr).unwrap().get_alive().unwrap();
assert_eq!(bob_contract.rent_allowance, <BalanceOf<Test>>::max_value());
ContractInfoOf::<Test>::get(&addr).unwrap().get_alive().unwrap();
// Call contract with allowed storage value.
assert_ok!(Contracts::call(
@@ -1795,7 +1813,7 @@ fn transfer_return_code() {
assert_ok!(
Contracts::instantiate(
Origin::signed(ALICE),
subsistence,
subsistence * 3,
GAS_LIMIT,
code_hash.into(),
vec![],
@@ -1805,6 +1823,7 @@ fn transfer_return_code() {
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
// Contract has only the minimal balance so any transfer will return BelowSubsistence.
Balances::make_free_balance_be(&addr, subsistence);
let result = Contracts::bare_call(
ALICE,
addr.clone(),
@@ -1844,7 +1863,7 @@ fn call_return_code() {
assert_ok!(
Contracts::instantiate(
Origin::signed(ALICE),
subsistence,
subsistence * 3,
GAS_LIMIT,
caller_hash.into(),
vec![0],
@@ -1852,6 +1871,7 @@ fn call_return_code() {
),
);
let addr_bob = Contracts::contract_address(&ALICE, &caller_hash, &[]);
Balances::make_free_balance_be(&addr_bob, subsistence);
// Contract calls into Django which is no valid contract
let result = Contracts::bare_call(
@@ -1866,7 +1886,7 @@ fn call_return_code() {
assert_ok!(
Contracts::instantiate(
Origin::signed(CHARLIE),
subsistence,
subsistence * 3,
GAS_LIMIT,
callee_hash.into(),
vec![0],
@@ -1874,6 +1894,7 @@ fn call_return_code() {
),
);
let addr_django = Contracts::contract_address(&CHARLIE, &callee_hash, &[]);
Balances::make_free_balance_be(&addr_django, subsistence);
// Contract has only the minimal balance so any transfer will return BelowSubsistence.
let result = Contracts::bare_call(
@@ -1938,7 +1959,7 @@ fn instantiate_return_code() {
assert_ok!(
Contracts::instantiate(
Origin::signed(ALICE),
subsistence,
subsistence * 3,
GAS_LIMIT,
caller_hash.into(),
vec![],
@@ -1948,6 +1969,7 @@ fn instantiate_return_code() {
let addr = Contracts::contract_address(&ALICE, &caller_hash, &[]);
// Contract has only the minimal balance so any transfer will return BelowSubsistence.
Balances::make_free_balance_be(&addr, subsistence);
let result = Contracts::bare_call(
ALICE,
addr.clone(),
@@ -2030,7 +2052,7 @@ fn disabled_chain_extension_errors_on_call() {
assert_ok!(
Contracts::instantiate(
Origin::signed(ALICE),
subsistence,
subsistence * 3,
GAS_LIMIT,
hash.into(),
vec![],
@@ -2061,7 +2083,7 @@ fn chain_extension_works() {
assert_ok!(
Contracts::instantiate(
Origin::signed(ALICE),
subsistence,
subsistence * 3,
GAS_LIMIT,
hash.into(),
vec![],
@@ -2132,7 +2154,7 @@ fn lazy_removal_works() {
assert_ok!(
Contracts::instantiate(
Origin::signed(ALICE),
subsistence,
subsistence * 3,
GAS_LIMIT,
hash.into(),
vec![],
@@ -2193,7 +2215,7 @@ fn lazy_removal_partial_remove_works() {
assert_ok!(
Contracts::instantiate(
Origin::signed(ALICE),
subsistence,
subsistence * 3,
GAS_LIMIT,
hash.into(),
vec![],
@@ -2275,7 +2297,7 @@ fn lazy_removal_does_no_run_on_full_block() {
assert_ok!(
Contracts::instantiate(
Origin::signed(ALICE),
subsistence,
subsistence * 3,
GAS_LIMIT,
hash.into(),
vec![],
@@ -2360,7 +2382,7 @@ fn lazy_removal_does_not_use_all_weight() {
assert_ok!(
Contracts::instantiate(
Origin::signed(ALICE),
subsistence,
subsistence * 3,
GAS_LIMIT,
hash.into(),
vec![],
@@ -2431,7 +2453,7 @@ fn deletion_queue_full() {
assert_ok!(
Contracts::instantiate(
Origin::signed(ALICE),
subsistence,
subsistence * 3,
GAS_LIMIT,
hash.into(),
vec![],
@@ -2472,3 +2494,36 @@ fn deletion_queue_full() {
<ContractInfoOf::<Test>>::get(&addr).unwrap().get_alive().unwrap();
});
}
#[test]
fn not_deployed_if_endowment_too_low_for_first_rent() {
let (wasm, code_hash) = compile_module::<Test>("set_rent").unwrap();
// The instantation deducted the rent for one block immediatly
let first_rent = <Test as Config>::RentFraction::get()
// base_deposit + deploy_set_storage (4 bytes in 1 item) - free_balance
.mul_ceil(80_000u32 + 40_000 + 10_000 - 30_000)
// blocks to rent
* 1;
ExtBuilder::default()
.existential_deposit(50)
.build()
.execute_with(|| {
// Create
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), wasm));
assert_storage_noop!(assert_err_ignore_postinfo!(Contracts::instantiate(
Origin::signed(ALICE),
30_000,
GAS_LIMIT, code_hash.into(),
(BalanceOf::<Test>::from(first_rent) - BalanceOf::<Test>::from(1u32))
.encode(), // rent allowance
vec![],
),
Error::<Test>::NewContractNotFunded,
));
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
assert_matches!(ContractInfoOf::<Test>::get(&addr), None);
});
}