Skip to content

Commit 4f11d19

Browse files
authored
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
1 parent 8e541bf commit 4f11d19

File tree

4 files changed

+221
-150
lines changed

4 files changed

+221
-150
lines changed

src/benchmarking/mod.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ benchmarks! {
336336
let s in 0 .. code::max_pages::<T>() * 64;
337337
let data = vec![42u8; (n * 1024) as usize];
338338
let salt = vec![42u8; (s * 1024) as usize];
339-
let endowment = ConfigCache::<T>::subsistence_threshold_uncached();
339+
let endowment = caller_funding::<T>() / 3u32.into();
340340
let caller = whitelisted_caller();
341341
T::Currency::make_free_balance_be(&caller, caller_funding::<T>());
342342
let WasmModule { code, hash, .. } = WasmModule::<T>::dummy_with_mem();
@@ -1373,7 +1373,7 @@ benchmarks! {
13731373
let hash_len = hashes.get(0).map(|x| x.encode().len()).unwrap_or(0);
13741374
let hashes_bytes = hashes.iter().flat_map(|x| x.encode()).collect::<Vec<_>>();
13751375
let hashes_len = hashes_bytes.len();
1376-
let value = ConfigCache::<T>::subsistence_threshold_uncached();
1376+
let value = Endow::max::<T>() / (r * API_BENCHMARK_BATCH_SIZE + 2).into();
13771377
assert!(value > 0u32.into());
13781378
let value_bytes = value.encode();
13791379
let value_len = value_bytes.len();
@@ -1457,7 +1457,8 @@ benchmarks! {
14571457
}: call(origin, callee, 0u32.into(), Weight::max_value(), vec![])
14581458
verify {
14591459
for addr in &addresses {
1460-
instance.alive_info()?;
1460+
ContractInfoOf::<T>::get(&addr).and_then(|c| c.get_alive())
1461+
.ok_or_else(|| "Contract should have been instantiated")?;
14611462
}
14621463
}
14631464

@@ -1493,7 +1494,7 @@ benchmarks! {
14931494
let input_len = inputs.get(0).map(|x| x.len()).unwrap_or(0);
14941495
let input_bytes = inputs.iter().cloned().flatten().collect::<Vec<_>>();
14951496
let inputs_len = input_bytes.len();
1496-
let value = ConfigCache::<T>::subsistence_threshold_uncached();
1497+
let value = Endow::max::<T>() / (API_BENCHMARK_BATCH_SIZE + 2).into();
14971498
assert!(value > 0u32.into());
14981499
let value_bytes = value.encode();
14991500
let value_len = value_bytes.len();

src/exec.rs

Lines changed: 66 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -322,53 +322,62 @@ where
322322
let caller = self.self_account.clone();
323323
let dest = Contracts::<T>::contract_address(&caller, code_hash, salt);
324324

325-
// TrieId has not been generated yet and storage is empty since contract is new.
326-
//
327-
// Generate it now.
328-
let dest_trie_id = Storage::<T>::generate_trie_id(&dest);
325+
let output = frame_support::storage::with_transaction(|| {
326+
// Generate the trie id in a new transaction to only increment the counter on success.
327+
let dest_trie_id = Storage::<T>::generate_trie_id(&dest);
329328

330-
let output = self.with_nested_context(dest.clone(), dest_trie_id, |nested| {
331-
Storage::<T>::place_contract(
332-
&dest,
333-
nested
334-
.self_trie_id
335-
.clone()
336-
.expect("the nested context always has to have self_trie_id"),
337-
code_hash.clone()
338-
)?;
339-
340-
// Send funds unconditionally here. If the `endowment` is below existential_deposit
341-
// then error will be returned here.
342-
transfer(
343-
TransferCause::Instantiate,
344-
transactor_kind,
345-
&caller,
346-
&dest,
347-
endowment,
348-
nested,
349-
)?;
350-
351-
let executable = nested.loader.load_init(&code_hash)
352-
.map_err(|_| Error::<T>::CodeNotFound)?;
353-
let output = nested.vm
354-
.execute(
355-
&executable,
356-
nested.new_call_context(caller.clone(), endowment),
357-
input_data,
358-
gas_meter,
359-
).map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee })?;
360-
361-
// We need each contract that exists to be above the subsistence threshold
362-
// in order to keep up the guarantuee that we always leave a tombstone behind
363-
// with the exception of a contract that called `seal_terminate`.
364-
if T::Currency::total_balance(&dest) < nested.config.subsistence_threshold() {
365-
Err(Error::<T>::NewContractNotFunded)?
366-
}
329+
let output = self.with_nested_context(dest.clone(), dest_trie_id, |nested| {
330+
Storage::<T>::place_contract(
331+
&dest,
332+
nested
333+
.self_trie_id
334+
.clone()
335+
.expect("the nested context always has to have self_trie_id"),
336+
code_hash.clone()
337+
)?;
338+
339+
// Send funds unconditionally here. If the `endowment` is below existential_deposit
340+
// then error will be returned here.
341+
transfer(
342+
TransferCause::Instantiate,
343+
transactor_kind,
344+
&caller,
345+
&dest,
346+
endowment,
347+
nested,
348+
)?;
349+
350+
let executable = nested.loader.load_init(&code_hash)
351+
.map_err(|_| Error::<T>::CodeNotFound)?;
352+
let output = nested.vm
353+
.execute(
354+
&executable,
355+
nested.new_call_context(caller.clone(), endowment),
356+
input_data,
357+
gas_meter,
358+
).map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee })?;
359+
360+
361+
// Collect the rent for the first block to prevent the creation of very large
362+
// contracts that never intended to pay for even one block.
363+
// This also makes sure that it is above the subsistence threshold
364+
// in order to keep up the guarantuee that we always leave a tombstone behind
365+
// with the exception of a contract that called `seal_terminate`.
366+
Rent::<T>::charge(&dest)?
367+
.and_then(|c| c.get_alive())
368+
.ok_or_else(|| Error::<T>::NewContractNotFunded)?;
369+
370+
// Deposit an instantiation event.
371+
deposit_event::<T>(vec![], RawEvent::Instantiated(caller.clone(), dest.clone()));
367372

368-
// Deposit an instantiation event.
369-
deposit_event::<T>(vec![], RawEvent::Instantiated(caller.clone(), dest.clone()));
373+
Ok(output)
374+
});
370375

371-
Ok(output)
376+
use frame_support::storage::TransactionOutcome::*;
377+
match output {
378+
Ok(_) => Commit(output),
379+
Err(_) => Rollback(output),
380+
}
372381
})?;
373382

374383
Ok((dest, output))
@@ -908,7 +917,7 @@ mod tests {
908917
let mut ctx = ExecutionContext::top_level(origin.clone(), &cfg, &vm, &loader);
909918
place_contract(&BOB, return_ch);
910919
set_balance(&origin, 100);
911-
set_balance(&dest, 0);
920+
let balance = get_balance(&dest);
912921

913922
let output = ctx.call(
914923
dest.clone(),
@@ -919,7 +928,9 @@ mod tests {
919928

920929
assert!(!output.is_success());
921930
assert_eq!(get_balance(&origin), 100);
922-
assert_eq!(get_balance(&dest), 0);
931+
932+
// the rent is still charged
933+
assert!(get_balance(&dest) < balance);
923934
});
924935
}
925936

@@ -1057,10 +1068,10 @@ mod tests {
10571068
let cfg = ConfigCache::preload();
10581069
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
10591070

1060-
set_balance(&ALICE, 100);
1071+
set_balance(&ALICE, cfg.subsistence_threshold() * 10);
10611072

10621073
let result = ctx.instantiate(
1063-
cfg.subsistence_threshold(),
1074+
cfg.subsistence_threshold() * 3,
10641075
&mut GasMeter::<Test>::new(GAS_LIMIT),
10651076
&input_data_ch,
10661077
vec![1, 2, 3, 4],
@@ -1307,7 +1318,7 @@ mod tests {
13071318
// Instantiate a contract and save it's address in `instantiated_contract_address`.
13081319
let (address, output) = ctx.ext.instantiate(
13091320
&dummy_ch,
1310-
ConfigCache::<Test>::subsistence_threshold_uncached(),
1321+
ConfigCache::<Test>::subsistence_threshold_uncached() * 3,
13111322
ctx.gas_meter,
13121323
vec![],
13131324
&[48, 49, 50],
@@ -1321,8 +1332,7 @@ mod tests {
13211332
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
13221333
let cfg = ConfigCache::preload();
13231334
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
1324-
set_balance(&ALICE, 1000);
1325-
set_balance(&BOB, 100);
1335+
set_balance(&ALICE, cfg.subsistence_threshold() * 100);
13261336
place_contract(&BOB, instantiator_ch);
13271337

13281338
assert_matches!(
@@ -1431,19 +1441,20 @@ mod tests {
14311441
let vm = MockVm::new();
14321442
let mut loader = MockLoader::empty();
14331443
let rent_allowance_ch = loader.insert(|ctx| {
1444+
let allowance = ConfigCache::<Test>::subsistence_threshold_uncached() * 3;
14341445
assert_eq!(ctx.ext.rent_allowance(), <BalanceOf<Test>>::max_value());
1435-
ctx.ext.set_rent_allowance(10);
1436-
assert_eq!(ctx.ext.rent_allowance(), 10);
1446+
ctx.ext.set_rent_allowance(allowance);
1447+
assert_eq!(ctx.ext.rent_allowance(), allowance);
14371448
exec_success()
14381449
});
14391450

14401451
ExtBuilder::default().build().execute_with(|| {
14411452
let cfg = ConfigCache::preload();
14421453
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
1443-
set_balance(&ALICE, 100);
1454+
set_balance(&ALICE, cfg.subsistence_threshold() * 10);
14441455

14451456
let result = ctx.instantiate(
1446-
cfg.subsistence_threshold(),
1457+
cfg.subsistence_threshold() * 5,
14471458
&mut GasMeter::<Test>::new(GAS_LIMIT),
14481459
&rent_allowance_ch,
14491460
vec![],

src/storage.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use codec::{Encode, Decode};
2727
use sp_std::prelude::*;
2828
use sp_std::marker::PhantomData;
2929
use sp_io::hashing::blake2_256;
30-
use sp_runtime::traits::Bounded;
30+
use sp_runtime::traits::{Bounded, Saturating};
3131
use sp_core::crypto::UncheckedFrom;
3232
use frame_support::{
3333
dispatch::DispatchResult,
@@ -182,7 +182,11 @@ where
182182
code_hash: ch,
183183
storage_size: 0,
184184
trie_id,
185-
deduct_block: <frame_system::Module<T>>::block_number(),
185+
deduct_block:
186+
// We want to charge rent for the first block in advance. Therefore we
187+
// treat the contract as if it was created in the last block and then
188+
// charge rent for it during instantation.
189+
<frame_system::Module<T>>::block_number().saturating_sub(1u32.into()),
186190
rent_allowance: <BalanceOf<T>>::max_value(),
187191
pair_count: 0,
188192
last_write: None,

0 commit comments

Comments
 (0)