Skip to content

Commit

Permalink
Charge write fees for expiration entry changes. (#1010)
Browse files Browse the repository at this point in the history
We charge these as a part of the rent fee, so it's effectively an additional flat component of the final rent cost. It's not going to be explicitly exposed or limited.
  • Loading branch information
dmkozh authored Aug 22, 2023
1 parent f5c6c0f commit 5dc7547
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 39 deletions.
59 changes: 47 additions & 12 deletions soroban-env-host/src/fees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,16 @@
/// This is technically not part of the Soroban host and is provided here for
/// the sake of sharing between the systems that run Soroban host (such as
/// Stellar core or Soroban RPC service).

/// Rough estimate of the base size of any transaction result in the archives
/// (independent of the transaction envelope size).
pub const TX_BASE_RESULT_SIZE: u32 = 300;
/// Estimate for any `ExpirationEntry` ledger entry, consisting of a 32-byte
/// key and entry with key and 4 bytes for expiration ledger.
pub const EXPIRATION_ENTRY_SIZE: u32 = 2 * 32 + 4;

const INSTRUCTIONS_INCREMENT: i64 = 10000;
const DATA_SIZE_1KB_INCREMENT: i64 = 1024;
const TX_BASE_RESULT_SIZE: u32 = 300;

/// These are the resource upper bounds specified by the Soroban transaction.
pub struct TransactionResources {
Expand Down Expand Up @@ -76,10 +83,12 @@ pub struct WriteFeeConfiguration {
pub struct LedgerEntryRentChange {
/// Whether this is persistent or temporary entry.
pub is_persistent: bool,
/// Size of the entry in bytes before it has been modified.
/// Size of the entry in bytes before it has been modified, including the
/// key.
/// `0` for newly-created entires.
pub old_size_bytes: u32,
/// Size of the entry in bytes after it has been modified.
/// Size of the entry in bytes after it has been modified, including the
/// key.
pub new_size_bytes: u32,
/// Expiration ledger of the entry before it has been modified.
/// Should be less than the current ledger for newly-created entires.
Expand All @@ -98,6 +107,9 @@ pub struct RentFeeConfiguration {
/// This is the same field as in `FeeConfiguration` and it has to be
/// computed via `compute_write_fee_per_1kb`.
pub fee_per_write_1kb: i64,
/// Fee per 1 entry written to ledger.
/// This is the same field as in `FeeConfiguration`.
pub fee_per_write_entry: i64,
/// Denominator for the total rent fee for persistent storage.
///
/// This can be thought of as the number of ledgers of rent that costs as
Expand Down Expand Up @@ -233,10 +245,31 @@ pub fn compute_rent_fee(
fee_config: &RentFeeConfiguration,
current_ledger_seq: u32,
) -> i64 {
let mut fee = 0;
let mut fee: i64 = 0;
let mut bumped_entries: i64 = 0;
let mut bumped_entry_key_size_bytes: u32 = 0;
for e in changed_entries {
fee += rent_fee_per_entry_change(e, fee_config, current_ledger_seq);
fee = fee.saturating_add(rent_fee_per_entry_change(e, fee_config, current_ledger_seq));
if e.old_expiration_ledger < e.new_expiration_ledger {
bumped_entries = bumped_entries.saturating_add(1);
bumped_entry_key_size_bytes =
bumped_entry_key_size_bytes.saturating_add(EXPIRATION_ENTRY_SIZE);
}
}
// The expiration bumps need to be written to the ledger. As they have
// constant size, we can charge for writing them independently of the actual
// entry size.
fee = fee.saturating_add(
fee_config
.fee_per_write_entry
.saturating_mul(bumped_entries),
);
fee = fee.saturating_add(compute_fee_per_increment(
bumped_entry_key_size_bytes,
fee_config.fee_per_write_1kb,
DATA_SIZE_1KB_INCREMENT,
));

fee
}

Expand All @@ -245,10 +278,10 @@ fn rent_fee_per_entry_change(
fee_config: &RentFeeConfiguration,
current_ledger: u32,
) -> i64 {
let mut fee = 0;
let mut fee: i64 = 0;
// Pay for the rent extension (if any).
if entry_change.new_expiration_ledger > entry_change.old_expiration_ledger {
fee += rent_fee_for_size_and_ledgers(
fee = fee.saturating_add(rent_fee_for_size_and_ledgers(
entry_change.is_persistent,
// New portion of rent is payed for the new size of the entry.
entry_change.new_size_bytes,
Expand All @@ -258,20 +291,20 @@ fn rent_fee_per_entry_change(
entry_change.new_expiration_ledger
- entry_change.old_expiration_ledger.max(current_ledger - 1),
fee_config,
);
));
}
// Pay for the entry size increase (if any).
if entry_change.new_size_bytes > entry_change.old_size_bytes && entry_change.old_size_bytes > 0
{
fee += rent_fee_for_size_and_ledgers(
fee = fee.saturating_add(rent_fee_for_size_and_ledgers(
entry_change.is_persistent,
// Pay only for the size increase.
entry_change.new_size_bytes - entry_change.old_size_bytes,
// Cover ledger interval [current; old], as (old, new] is already
// covered above for the whole new size.
entry_change.old_expiration_ledger - current_ledger + 1,
fee_config,
);
));
}

fee
Expand All @@ -286,13 +319,15 @@ fn rent_fee_for_size_and_ledgers(
// Multiplication can overflow here - unlike fee computation this can rely
// on sane input parameters as rent fee computation does not depend on any
// user inputs.
let num = entry_size as i64 * fee_config.fee_per_write_1kb * rent_ledgers as i64;
let num = (entry_size as i64)
.saturating_mul(fee_config.fee_per_write_1kb)
.saturating_mul(rent_ledgers as i64);
let storage_coef = if is_persistent {
fee_config.persistent_rent_rate_denominator
} else {
fee_config.temporary_rent_rate_denominator
};
let denom = DATA_SIZE_1KB_INCREMENT * storage_coef;
let denom = DATA_SIZE_1KB_INCREMENT.saturating_mul(storage_coef);
num_integer::div_ceil(num, denom)
}

Expand Down
88 changes: 61 additions & 27 deletions soroban-env-host/tests/fees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ fn resource_fee_computation() {
#[test]
fn test_rent_bump_fees_with_only_bump() {
let fee_config = RentFeeConfiguration {
fee_per_write_entry: 10,
fee_per_write_1kb: 1000,
persistent_rent_rate_denominator: 10_000,
temporary_rent_rate_denominator: 100_000,
Expand All @@ -139,8 +140,10 @@ fn test_rent_bump_fees_with_only_bump() {
&fee_config,
50_000,
),
// 1 * 1000 * 200_000 / (10_000 * 1024)
20
// Rent: ceil(1 * 1000 * 200_000 / (10_000 * 1024)) (=20) +
// Expiration entry write bytes: ceil(1000 * 68 / 1024) (=67) +
// Expiration entry write: 10
20 + 67 + 10
);

// Minimal ledgers
Expand All @@ -156,8 +159,9 @@ fn test_rent_bump_fees_with_only_bump() {
&fee_config,
50_000,
),
// ceil(10 * 1024 * 1000 * 1 / (10_000 * 1024))
1
// Rent: ceil(10 * 1024 * 1000 * 1 / (10_000 * 1024)) (=1) +
// Expiration entry write entry/bytes: 77
1 + 77
);

// Minimal ledgers & size
Expand All @@ -173,8 +177,9 @@ fn test_rent_bump_fees_with_only_bump() {
&fee_config,
50_000,
),
// ceil(1 * 1000 * 1 / (10_000 * 1024))
1
// Rent: ceil(1 * 1000 * 1 / (10_000 * 1024))
// Expiration entry write entry/bytes: 77
1 + 77
);

// No size change
Expand All @@ -190,8 +195,9 @@ fn test_rent_bump_fees_with_only_bump() {
&fee_config,
50_000,
),
// 10 * 1024 * 1000 * 200_000 / (10_000 * 1024)
200_000
// Rent: ceil(10 * 1024 * 1000 * 200_000 / (10_000 * 1024)) (=200_000)
// Expiration entry write entry/bytes: 77
200_000 + 77
);

// Size decrease
Expand All @@ -207,8 +213,9 @@ fn test_rent_bump_fees_with_only_bump() {
&fee_config,
50_000,
),
// 5 * 1024 * 1000 * 200_000 / (10_000 * 1024)
100_000
// Rent: ceil(5 * 1024 * 1000 * 200_000 / (10_000 * 1024)) (=100_000) +
// Expiration entry write entry/bytes: 77
100_000 + 77
);

// Temp storage rate
Expand All @@ -224,8 +231,9 @@ fn test_rent_bump_fees_with_only_bump() {
&fee_config,
50_000,
),
// 10 * 1024 * 1000 * 200_000 / (100_000 * 1024)
20_000
// Rent: ceil(10 * 1024 * 1000 * 200_000 / (100_000 * 1024)) (=20_000) +
// Expiration entry write entry/bytes: 77
20_000 + 77
);

// Multiple entries
Expand Down Expand Up @@ -259,19 +267,36 @@ fn test_rent_bump_fees_with_only_bump() {
new_size_bytes: 1,
old_expiration_ledger: 100_000,
new_expiration_ledger: 300_000,
},
LedgerEntryRentChange {
is_persistent: true,
old_size_bytes: 10 * 1024,
new_size_bytes: 10 * 1024,
old_expiration_ledger: 100_000,
new_expiration_ledger: 300_000,
},
LedgerEntryRentChange {
is_persistent: false,
old_size_bytes: 10 * 1024,
new_size_bytes: 10 * 1024,
old_expiration_ledger: 100_000,
new_expiration_ledger: 300_000,
}
],
&fee_config,
50_000,
),
// 20_000 + 200_000 + 1 + 20
220_021
// Rent: 20_000 + 200_000 + 1 + 20 + 200_000 + 20_000 (=440_021) +
// Expiration entry write bytes: ceil(6 * 1000 * 68 / 1024) (=399) +
// Expiration entry write: 10 * 6
440_021 + 399 + 60
);
}

#[test]
fn test_rent_bump_fees_with_only_size_change() {
let fee_config = RentFeeConfiguration {
fee_per_write_entry: 100,
fee_per_write_1kb: 1000,
persistent_rent_rate_denominator: 10_000,
temporary_rent_rate_denominator: 100_000,
Expand Down Expand Up @@ -375,6 +400,7 @@ fn test_rent_bump_fees_with_only_size_change() {
#[test]
fn test_rent_bump_with_size_change_and_bump() {
let fee_config = RentFeeConfiguration {
fee_per_write_entry: 10,
fee_per_write_1kb: 1000,
persistent_rent_rate_denominator: 10_000,
temporary_rent_rate_denominator: 100_000,
Expand All @@ -393,9 +419,10 @@ fn test_rent_bump_with_size_change_and_bump() {
&fee_config,
25_000,
),
// 100_000 * 1000 * 200_000 / (10_000 * 1024) +
// Rent: 100_000 * 1000 * 200_000 / (10_000 * 1024) +
// 99_999 * 1000 * (100_000 - 25_000 + 1) / (10_000 * 1024)
2_685_550
// Expiration entry write entry/bytes: 77
2_685_550 + 77
);

// Temp entry
Expand All @@ -411,9 +438,10 @@ fn test_rent_bump_with_size_change_and_bump() {
&fee_config,
25_000,
),
// 100_000 * 1000 * 200_000 / (10_000 * 1024) +
// Rent: 100_000 * 1000 * 200_000 / (10_000 * 1024) +
// 99_999 * 1000 * (100_000 - 25_000 + 1) / (10_000 * 1024)
268_556
// Expiration entry write entry/bytes: 77
268_556 + 77
);

// Multiple entries
Expand All @@ -438,8 +466,10 @@ fn test_rent_bump_with_size_change_and_bump() {
&fee_config,
25_000,
),
// 2_685_550 + 268_556
2_954_106
// Rent: 2_685_550 + 268_556
// Expiration entry write bytes: ceil(2 * 1000 * 68 / 1024) (=133) +
// Expiration entry write: 10 * 2
2_954_106 + 133 + 20
);

// Small increments
Expand All @@ -455,15 +485,17 @@ fn test_rent_bump_with_size_change_and_bump() {
&fee_config,
99_999,
),
// ceil(2 * 1000 * 1 / (10_000 * 1024)) +
// ceil(1 * 1000 * (100_000 - 99_999 + 1) / (10_000 * 1024))
2
// Rent: ceil(2 * 1000 * 1 / (10_000 * 1024)) +
// ceil(1 * 1000 * (100_000 - 99_999 + 1) / (10_000 * 1024)) (=2)
// Expiration entry write entry/bytes: 77
2 + 77
);
}

#[test]
fn test_rent_bump_without_old_entry() {
let fee_config = RentFeeConfiguration {
fee_per_write_entry: 10,
fee_per_write_1kb: 1000,
persistent_rent_rate_denominator: 10_000,
temporary_rent_rate_denominator: 100_000,
Expand All @@ -482,8 +514,9 @@ fn test_rent_bump_without_old_entry() {
&fee_config,
25_000,
),
// 100_000 * 1000 * (100_000 - 25_000) / (10_000 * 1024)
732_432
// Rent: 100_000 * 1000 * (100_000 - 25_000) / (10_000 * 1024)
// Expiration entry write entry/bytes: 77
732_432 + 77
);

// Temp storage
Expand All @@ -499,8 +532,9 @@ fn test_rent_bump_without_old_entry() {
&fee_config,
25_000,
),
// 100_000 * 1000 * (100_000 - 25_000) / (10_000 * 1024)
73_244
// Rent: 100_000 * 1000 * (100_000 - 25_000) / (10_000 * 1024)
// Expiration entry write entry/bytes: 77
73_244 + 77
);
}

Expand Down

0 comments on commit 5dc7547

Please sign in to comment.