Skip to content

Commit 8e541bf

Browse files
authored
contracts: Add configurable per-storage item cost (#7819)
* Rework rent parameters * No need for empty_pair_count any longer * Parameterize runtime
1 parent 65ab8ca commit 8e541bf

File tree

5 files changed

+91
-105
lines changed

5 files changed

+91
-105
lines changed

src/benchmarking/mod.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,12 +116,13 @@ where
116116
// the subsistence threshold does not pay rent given a large enough subsistence
117117
// threshold. But we need rent payments to occur in order to benchmark for worst cases.
118118
let storage_size = ConfigCache::<T>::subsistence_threshold_uncached()
119-
.checked_div(&T::RentDepositOffset::get())
119+
.checked_div(&T::DepositPerStorageByte::get())
120120
.unwrap_or_else(Zero::zero);
121121

122122
// Endowment should be large but not as large to inhibit rent payments.
123-
let endowment = T::RentDepositOffset::get()
124-
.saturating_mul(storage_size + T::StorageSizeOffset::get().into())
123+
let endowment = T::DepositPerStorageByte::get()
124+
.saturating_mul(storage_size)
125+
.saturating_add(T::DepositPerContract::get())
125126
.saturating_sub(1u32.into());
126127

127128
(storage_size, endowment)

src/lib.rs

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ use sp_runtime::{
115115
traits::{
116116
Hash, StaticLookup, Zero, MaybeSerializeDeserialize, Member, Convert, Saturating,
117117
},
118-
RuntimeDebug,
118+
RuntimeDebug, Perbill,
119119
};
120120
use frame_support::{
121121
decl_module, decl_event, decl_storage, decl_error, ensure,
@@ -205,11 +205,8 @@ pub struct RawAliveContractInfo<CodeHash, Balance, BlockNumber> {
205205
///
206206
/// It is a sum of each key-value pair stored by this contract.
207207
pub storage_size: u32,
208-
/// The number of key-value pairs that have values of zero length.
209-
/// The condition `empty_pair_count ≤ total_pair_count` always holds.
210-
pub empty_pair_count: u32,
211208
/// The total number of key-value pairs in storage of this contract.
212-
pub total_pair_count: u32,
209+
pub pair_count: u32,
213210
/// The code associated with a given account.
214211
pub code_hash: CodeHash,
215212
/// Pay rent at most up to this value.
@@ -286,24 +283,35 @@ pub trait Config: frame_system::Config {
286283
/// The minimum amount required to generate a tombstone.
287284
type TombstoneDeposit: Get<BalanceOf<Self>>;
288285

289-
/// A size offset for an contract. A just created account with untouched storage will have that
290-
/// much of storage from the perspective of the state rent.
286+
/// The balance every contract needs to deposit to stay alive indefinitely.
287+
///
288+
/// This is different from the [`Self::TombstoneDeposit`] because this only needs to be
289+
/// deposited while the contract is alive. Costs for additional storage are added to
290+
/// this base cost.
291291
///
292292
/// This is a simple way to ensure that contracts with empty storage eventually get deleted by
293293
/// making them pay rent. This creates an incentive to remove them early in order to save rent.
294-
type StorageSizeOffset: Get<u32>;
295-
296-
/// Price of a byte of storage per one block interval. Should be greater than 0.
297-
type RentByteFee: Get<BalanceOf<Self>>;
294+
type DepositPerContract: Get<BalanceOf<Self>>;
298295

299-
/// The amount of funds a contract should deposit in order to offset
300-
/// the cost of one byte.
296+
/// The balance a contract needs to deposit per storage byte to stay alive indefinitely.
301297
///
302298
/// Let's suppose the deposit is 1,000 BU (balance units)/byte and the rent is 1 BU/byte/day,
303299
/// then a contract with 1,000,000 BU that uses 1,000 bytes of storage would pay no rent.
304300
/// But if the balance reduced to 500,000 BU and the storage stayed the same at 1,000,
305301
/// then it would pay 500 BU/day.
306-
type RentDepositOffset: Get<BalanceOf<Self>>;
302+
type DepositPerStorageByte: Get<BalanceOf<Self>>;
303+
304+
/// The balance a contract needs to deposit per storage item to stay alive indefinitely.
305+
///
306+
/// It works the same as [`Self::DepositPerStorageByte`] but for storage items.
307+
type DepositPerStorageItem: Get<BalanceOf<Self>>;
308+
309+
/// The fraction of the deposit that should be used as rent per block.
310+
///
311+
/// When a contract hasn't enough balance deposited to stay alive indefinitely it needs
312+
/// to pay per block for the storage it consumes that is not covered by the deposit.
313+
/// This determines how high this rent payment is per block as a fraction of the deposit.
314+
type RentFraction: Get<Perbill>;
307315

308316
/// Reward that is received by the party whose touch has led
309317
/// to removal of a contract.
@@ -435,25 +443,35 @@ decl_module! {
435443
/// The minimum amount required to generate a tombstone.
436444
const TombstoneDeposit: BalanceOf<T> = T::TombstoneDeposit::get();
437445

438-
/// A size offset for an contract. A just created account with untouched storage will have that
439-
/// much of storage from the perspective of the state rent.
446+
/// The balance every contract needs to deposit to stay alive indefinitely.
440447
///
441-
/// This is a simple way to ensure that contracts with empty storage eventually get deleted
442-
/// by making them pay rent. This creates an incentive to remove them early in order to save
443-
/// rent.
444-
const StorageSizeOffset: u32 = T::StorageSizeOffset::get();
445-
446-
/// Price of a byte of storage per one block interval. Should be greater than 0.
447-
const RentByteFee: BalanceOf<T> = T::RentByteFee::get();
448+
/// This is different from the [`Self::TombstoneDeposit`] because this only needs to be
449+
/// deposited while the contract is alive. Costs for additional storage are added to
450+
/// this base cost.
451+
///
452+
/// This is a simple way to ensure that contracts with empty storage eventually get deleted by
453+
/// making them pay rent. This creates an incentive to remove them early in order to save rent.
454+
const DepositPerContract: BalanceOf<T> = T::DepositPerContract::get();
448455

449-
/// The amount of funds a contract should deposit in order to offset
450-
/// the cost of one byte.
456+
/// The balance a contract needs to deposit per storage byte to stay alive indefinitely.
451457
///
452458
/// Let's suppose the deposit is 1,000 BU (balance units)/byte and the rent is 1 BU/byte/day,
453459
/// then a contract with 1,000,000 BU that uses 1,000 bytes of storage would pay no rent.
454460
/// But if the balance reduced to 500,000 BU and the storage stayed the same at 1,000,
455461
/// then it would pay 500 BU/day.
456-
const RentDepositOffset: BalanceOf<T> = T::RentDepositOffset::get();
462+
const DepositPerStorageByte: BalanceOf<T> = T::DepositPerStorageByte::get();
463+
464+
/// The balance a contract needs to deposit per storage item to stay alive indefinitely.
465+
///
466+
/// It works the same as [`Self::DepositPerStorageByte`] but for storage items.
467+
const DepositPerStorageItem: BalanceOf<T> = T::DepositPerStorageItem::get();
468+
469+
/// The fraction of the deposit that should be used as rent per block.
470+
///
471+
/// When a contract hasn't enough balance deposited to stay alive indefinitely it needs
472+
/// to pay per block for the storage it consumes that is not covered by the deposit.
473+
/// This determines how high this rent payment is per block as a fraction of the deposit.
474+
const RentFraction: Perbill = T::RentFraction::get();
457475

458476
/// Reward that is received by the party whose touch has led
459477
/// to removal of a contract.

src/rent.rs

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -101,21 +101,15 @@ where
101101
free_balance: &BalanceOf<T>,
102102
contract: &AliveContractInfo<T>
103103
) -> BalanceOf<T> {
104-
let free_storage = free_balance
105-
.checked_div(&T::RentDepositOffset::get())
106-
.unwrap_or_else(Zero::zero);
107-
108-
// For now, we treat every empty KV pair as if it was one byte long.
109-
let empty_pairs_equivalent = contract.empty_pair_count;
110-
111-
let effective_storage_size = <BalanceOf<T>>::from(
112-
contract.storage_size + T::StorageSizeOffset::get() + empty_pairs_equivalent,
113-
)
114-
.saturating_sub(free_storage);
115-
116-
effective_storage_size
117-
.checked_mul(&T::RentByteFee::get())
118-
.unwrap_or_else(|| <BalanceOf<T>>::max_value())
104+
let uncovered_by_balance = T::DepositPerStorageByte::get()
105+
.saturating_mul(contract.storage_size.into())
106+
.saturating_add(
107+
T::DepositPerStorageItem::get()
108+
.saturating_mul(contract.pair_count.into())
109+
)
110+
.saturating_add(T::DepositPerContract::get())
111+
.saturating_sub(*free_balance);
112+
T::RentFraction::get().mul_ceil(uncovered_by_balance)
119113
}
120114

121115
/// Returns amount of funds available to consume by rent mechanism.
@@ -484,8 +478,7 @@ where
484478
<ContractInfoOf<T>>::insert(&dest, ContractInfo::Alive(AliveContractInfo::<T> {
485479
trie_id: origin_contract.trie_id,
486480
storage_size: origin_contract.storage_size,
487-
empty_pair_count: origin_contract.empty_pair_count,
488-
total_pair_count: origin_contract.total_pair_count,
481+
pair_count: origin_contract.pair_count,
489482
code_hash,
490483
rent_allowance,
491484
deduct_block: current_block,

src/storage.rs

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -102,27 +102,14 @@ where
102102

103103
// Update the total number of KV pairs and the number of empty pairs.
104104
match (&opt_prev_value, &opt_new_value) {
105-
(Some(prev_value), None) => {
106-
new_info.total_pair_count -= 1;
107-
if prev_value.is_empty() {
108-
new_info.empty_pair_count -= 1;
109-
}
105+
(Some(_), None) => {
106+
new_info.pair_count -= 1;
110107
},
111-
(None, Some(new_value)) => {
112-
new_info.total_pair_count += 1;
113-
if new_value.is_empty() {
114-
new_info.empty_pair_count += 1;
115-
}
108+
(None, Some(_)) => {
109+
new_info.pair_count += 1;
116110
},
117-
(Some(prev_value), Some(new_value)) => {
118-
if prev_value.is_empty() {
119-
new_info.empty_pair_count -= 1;
120-
}
121-
if new_value.is_empty() {
122-
new_info.empty_pair_count += 1;
123-
}
124-
}
125-
(None, None) => {}
111+
(Some(_), Some(_)) => {},
112+
(None, None) => {},
126113
}
127114

128115
// Update the total storage size.
@@ -197,8 +184,7 @@ where
197184
trie_id,
198185
deduct_block: <frame_system::Module<T>>::block_number(),
199186
rent_allowance: <BalanceOf<T>>::max_value(),
200-
empty_pair_count: 0,
201-
total_pair_count: 0,
187+
pair_count: 0,
202188
last_write: None,
203189
}
204190
.into(),
@@ -217,7 +203,7 @@ where
217203
Err(Error::<T>::DeletionQueueFull.into())
218204
} else {
219205
DeletionQueue::append(DeletedContract {
220-
pair_count: contract.total_pair_count,
206+
pair_count: contract.pair_count,
221207
trie_id: contract.trie_id.clone(),
222208
});
223209
Ok(())

src/tests.rs

Lines changed: 25 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ use codec::Encode;
3030
use sp_runtime::{
3131
traits::{BlakeTwo256, Hash, IdentityLookup, Convert},
3232
testing::{Header, H256},
33-
AccountId32,
33+
AccountId32, Perbill,
3434
};
3535
use sp_io::hashing::blake2_256;
3636
use frame_support::{
@@ -239,9 +239,10 @@ impl pallet_timestamp::Config for Test {
239239
parameter_types! {
240240
pub const SignedClaimHandicap: u64 = 2;
241241
pub const TombstoneDeposit: u64 = 16;
242-
pub const StorageSizeOffset: u32 = 8;
243-
pub const RentByteFee: u64 = 4;
244-
pub const RentDepositOffset: u64 = 10_000;
242+
pub const DepositPerContract: u64 = 8 * DepositPerStorageByte::get();
243+
pub const DepositPerStorageByte: u64 = 10_000;
244+
pub const DepositPerStorageItem: u64 = 10_000;
245+
pub RentFraction: Perbill = Perbill::from_rational_approximation(4u32, 10_000u32);
245246
pub const SurchargeReward: u64 = 150;
246247
pub const MaxDepth: u32 = 100;
247248
pub const MaxValueSize: u32 = 16_384;
@@ -267,9 +268,10 @@ impl Config for Test {
267268
type RentPayment = ();
268269
type SignedClaimHandicap = SignedClaimHandicap;
269270
type TombstoneDeposit = TombstoneDeposit;
270-
type StorageSizeOffset = StorageSizeOffset;
271-
type RentByteFee = RentByteFee;
272-
type RentDepositOffset = RentDepositOffset;
271+
type DepositPerContract = DepositPerContract;
272+
type DepositPerStorageByte = DepositPerStorageByte;
273+
type DepositPerStorageItem = DepositPerStorageItem;
274+
type RentFraction = RentFraction;
273275
type SurchargeReward = SurchargeReward;
274276
type MaxDepth = MaxDepth;
275277
type MaxValueSize = MaxValueSize;
@@ -384,8 +386,7 @@ fn account_removal_does_not_remove_storage() {
384386
let alice_contract_info = ContractInfo::Alive(RawAliveContractInfo {
385387
trie_id: trie_id1.clone(),
386388
storage_size: 0,
387-
empty_pair_count: 0,
388-
total_pair_count: 0,
389+
pair_count: 0,
389390
deduct_block: System::block_number(),
390391
code_hash: H256::repeat_byte(1),
391392
rent_allowance: 40,
@@ -399,8 +400,7 @@ fn account_removal_does_not_remove_storage() {
399400
let bob_contract_info = ContractInfo::Alive(RawAliveContractInfo {
400401
trie_id: trie_id2.clone(),
401402
storage_size: 0,
402-
empty_pair_count: 0,
403-
total_pair_count: 0,
403+
pair_count: 0,
404404
deduct_block: System::block_number(),
405405
code_hash: H256::repeat_byte(2),
406406
rent_allowance: 40,
@@ -690,13 +690,9 @@ fn storage_size() {
690690
4
691691
);
692692
assert_eq!(
693-
bob_contract.total_pair_count,
693+
bob_contract.pair_count,
694694
1,
695695
);
696-
assert_eq!(
697-
bob_contract.empty_pair_count,
698-
0,
699-
);
700696

701697
assert_ok!(Contracts::call(
702698
Origin::signed(ALICE),
@@ -714,13 +710,9 @@ fn storage_size() {
714710
4 + 4
715711
);
716712
assert_eq!(
717-
bob_contract.total_pair_count,
713+
bob_contract.pair_count,
718714
2,
719715
);
720-
assert_eq!(
721-
bob_contract.empty_pair_count,
722-
0,
723-
);
724716

725717
assert_ok!(Contracts::call(
726718
Origin::signed(ALICE),
@@ -738,13 +730,9 @@ fn storage_size() {
738730
4
739731
);
740732
assert_eq!(
741-
bob_contract.total_pair_count,
733+
bob_contract.pair_count,
742734
1,
743735
);
744-
assert_eq!(
745-
bob_contract.empty_pair_count,
746-
0,
747-
);
748736
});
749737
}
750738

@@ -776,11 +764,7 @@ fn empty_kv_pairs() {
776764
0,
777765
);
778766
assert_eq!(
779-
bob_contract.total_pair_count,
780-
1,
781-
);
782-
assert_eq!(
783-
bob_contract.empty_pair_count,
767+
bob_contract.pair_count,
784768
1,
785769
);
786770
});
@@ -828,9 +812,11 @@ fn deduct_blocks() {
828812
);
829813

830814
// Check result
831-
let rent = (8 + 4 - 3) // storage size = size_offset + deploy_set_storage - deposit_offset
832-
* 4 // rent byte price
833-
* 4; // blocks to rent
815+
let rent = <Test as Config>::RentFraction::get()
816+
// base_deposit + deploy_set_storage (4 bytes in 1 item) - free_balance
817+
.mul_ceil(80_000 + 40_000 + 10_000 - 30_000)
818+
// blocks to rent
819+
* 4;
834820
let bob_contract = ContractInfoOf::<Test>::get(&addr).unwrap().get_alive().unwrap();
835821
assert_eq!(bob_contract.rent_allowance, 1_000 - rent);
836822
assert_eq!(bob_contract.deduct_block, 5);
@@ -845,9 +831,11 @@ fn deduct_blocks() {
845831
);
846832

847833
// Check result
848-
let rent_2 = (8 + 4 - 2) // storage size = size_offset + deploy_set_storage - deposit_offset
849-
* 4 // rent byte price
850-
* 7; // blocks to rent
834+
let rent_2 = <Test as Config>::RentFraction::get()
835+
// base_deposit + deploy_set_storage (4 bytes in 1 item) - free_balance
836+
.mul_ceil(80_000 + 40_000 + 10_000 - (30_000 - rent))
837+
// blocks to rent
838+
* 7;
851839
let bob_contract = ContractInfoOf::<Test>::get(&addr).unwrap().get_alive().unwrap();
852840
assert_eq!(bob_contract.rent_allowance, 1_000 - rent - rent_2);
853841
assert_eq!(bob_contract.deduct_block, 12);

0 commit comments

Comments
 (0)