1pub const TX_BASE_RESULT_SIZE: u32 = 300;
10pub const TTL_ENTRY_SIZE: u32 = 48;
12
13pub const INSTRUCTIONS_INCREMENT: i64 = 10000;
14pub const DATA_SIZE_1KB_INCREMENT: i64 = 1024;
15
16pub const MINIMUM_RENT_WRITE_FEE_PER_1KB: i64 = 1000;
18
19pub const CODE_ENTRY_RENT_DISCOUNT_FACTOR: i64 = 3;
24
25pub struct TransactionResources {
27 pub instructions: u32,
29 pub disk_read_entries: u32,
31 pub write_entries: u32,
34 pub disk_read_bytes: u32,
36 pub write_bytes: u32,
38 pub contract_events_size_bytes: u32,
40 pub transaction_size_bytes: u32,
42}
43
44#[derive(Debug, Default, PartialEq, Eq)]
51pub struct FeeConfiguration {
52 pub fee_per_instruction_increment: i64,
54 pub fee_per_disk_read_entry: i64,
56 pub fee_per_write_entry: i64,
58 pub fee_per_disk_read_1kb: i64,
60 pub fee_per_write_1kb: i64,
62 pub fee_per_historical_1kb: i64,
65 pub fee_per_contract_event_1kb: i64,
67 pub fee_per_transaction_size_1kb: i64,
69}
70
71#[derive(Debug, Default, PartialEq, Eq)]
76pub struct RentWriteFeeConfiguration {
77 pub state_target_size_bytes: i64,
79 pub rent_fee_1kb_state_size_low: i64,
81 pub rent_fee_1kb_state_size_high: i64,
84 pub state_size_rent_fee_growth_factor: u32,
87}
88
89pub struct LedgerEntryRentChange {
95 pub is_persistent: bool,
97 pub is_code_entry: bool,
99 pub old_size_bytes: u32,
102 pub new_size_bytes: u32,
105 pub old_live_until_ledger: u32,
108 pub new_live_until_ledger: u32,
111}
112
113#[derive(Debug, Default, PartialEq, Eq)]
120pub struct RentFeeConfiguration {
121 pub fee_per_write_1kb: i64,
124 pub fee_per_rent_1kb: i64,
127 pub fee_per_write_entry: i64,
130 pub persistent_rent_rate_denominator: i64,
135 pub temporary_rent_rate_denominator: i64,
139}
140
141pub fn compute_transaction_resource_fee(
150 tx_resources: &TransactionResources,
151 fee_config: &FeeConfiguration,
152) -> (i64, i64) {
153 let compute_fee = compute_fee_per_increment(
154 tx_resources.instructions,
155 fee_config.fee_per_instruction_increment,
156 INSTRUCTIONS_INCREMENT,
157 );
158 let ledger_read_entry_fee: i64 = fee_config
159 .fee_per_disk_read_entry
160 .saturating_mul(tx_resources.disk_read_entries.into());
161 let ledger_write_entry_fee = fee_config
162 .fee_per_write_entry
163 .saturating_mul(tx_resources.write_entries.into());
164 let ledger_read_bytes_fee = compute_fee_per_increment(
165 tx_resources.disk_read_bytes,
166 fee_config.fee_per_disk_read_1kb,
167 DATA_SIZE_1KB_INCREMENT,
168 );
169 let ledger_write_bytes_fee = compute_fee_per_increment(
170 tx_resources.write_bytes,
171 fee_config.fee_per_write_1kb,
172 DATA_SIZE_1KB_INCREMENT,
173 );
174
175 let historical_fee = compute_fee_per_increment(
176 tx_resources
177 .transaction_size_bytes
178 .saturating_add(TX_BASE_RESULT_SIZE),
179 fee_config.fee_per_historical_1kb,
180 DATA_SIZE_1KB_INCREMENT,
181 );
182
183 let events_fee = compute_fee_per_increment(
184 tx_resources.contract_events_size_bytes,
185 fee_config.fee_per_contract_event_1kb,
186 DATA_SIZE_1KB_INCREMENT,
187 );
188
189 let bandwidth_fee = compute_fee_per_increment(
190 tx_resources.transaction_size_bytes,
191 fee_config.fee_per_transaction_size_1kb,
192 DATA_SIZE_1KB_INCREMENT,
193 );
194
195 let refundable_fee = events_fee;
196 let non_refundable_fee = compute_fee
197 .saturating_add(ledger_read_entry_fee)
198 .saturating_add(ledger_write_entry_fee)
199 .saturating_add(ledger_read_bytes_fee)
200 .saturating_add(ledger_write_bytes_fee)
201 .saturating_add(historical_fee)
202 .saturating_add(bandwidth_fee);
203
204 (non_refundable_fee, refundable_fee)
205}
206
207trait ClampFee {
210 fn clamp_fee(self) -> i64;
211}
212
213impl ClampFee for i64 {
214 fn clamp_fee(self) -> i64 {
215 if self < 0 {
216 i64::MAX
223 } else {
224 self
225 }
226 }
227}
228
229impl ClampFee for i128 {
230 fn clamp_fee(self) -> i64 {
231 if self < 0 {
232 i64::MAX
233 } else {
234 i64::try_from(self).unwrap_or(i64::MAX)
235 }
236 }
237}
238
239pub fn compute_rent_write_fee_per_1kb(
246 soroban_state_size_bytes: i64,
247 fee_config: &RentWriteFeeConfiguration,
248) -> i64 {
249 let fee_rate_multiplier = fee_config
250 .rent_fee_1kb_state_size_high
251 .saturating_sub(fee_config.rent_fee_1kb_state_size_low)
252 .clamp_fee();
253 let mut rent_write_fee_per_1kb: i64;
254 if soroban_state_size_bytes < fee_config.state_target_size_bytes {
255 rent_write_fee_per_1kb = num_integer::div_ceil(
258 (fee_rate_multiplier as i128).saturating_mul(soroban_state_size_bytes as i128),
259 (fee_config.state_target_size_bytes as i128).max(1),
260 )
261 .clamp_fee();
262 rent_write_fee_per_1kb =
264 rent_write_fee_per_1kb.saturating_add(fee_config.rent_fee_1kb_state_size_low);
265 } else {
266 rent_write_fee_per_1kb = fee_config.rent_fee_1kb_state_size_high;
267 let bucket_list_size_after_reaching_target =
268 soroban_state_size_bytes.saturating_sub(fee_config.state_target_size_bytes);
269 let post_target_fee = num_integer::div_ceil(
270 (fee_rate_multiplier as i128)
271 .saturating_mul(bucket_list_size_after_reaching_target as i128)
272 .saturating_mul(fee_config.state_size_rent_fee_growth_factor as i128),
273 (fee_config.state_target_size_bytes as i128).max(1),
274 )
275 .clamp_fee();
276 rent_write_fee_per_1kb = rent_write_fee_per_1kb.saturating_add(post_target_fee);
277 }
278
279 rent_write_fee_per_1kb.max(MINIMUM_RENT_WRITE_FEE_PER_1KB)
280}
281
282pub fn compute_rent_fee(
291 changed_entries: &[LedgerEntryRentChange],
292 fee_config: &RentFeeConfiguration,
293 current_ledger_seq: u32,
294) -> i64 {
295 let mut fee: i64 = 0;
296 let mut extended_entries: i64 = 0;
297 let mut extended_entry_key_size_bytes: u32 = 0;
298 for e in changed_entries {
299 fee = fee.saturating_add(rent_fee_per_entry_change(e, fee_config, current_ledger_seq));
300 if e.old_live_until_ledger < e.new_live_until_ledger {
301 extended_entries = extended_entries.saturating_add(1);
302 extended_entry_key_size_bytes =
303 extended_entry_key_size_bytes.saturating_add(TTL_ENTRY_SIZE);
304 }
305 }
306 fee = fee.saturating_add(
310 fee_config
311 .fee_per_write_entry
312 .saturating_mul(extended_entries),
313 );
314 fee = fee.saturating_add(compute_fee_per_increment(
315 extended_entry_key_size_bytes,
316 fee_config.fee_per_write_1kb,
317 DATA_SIZE_1KB_INCREMENT,
318 ));
319
320 fee
321}
322
323fn exclusive_ledger_diff(lo: u32, hi: u32) -> Option<u32> {
325 hi.checked_sub(lo)
326}
327
328fn inclusive_ledger_diff(lo: u32, hi: u32) -> Option<u32> {
330 exclusive_ledger_diff(lo, hi).map(|diff| diff.saturating_add(1))
331}
332
333impl LedgerEntryRentChange {
334 fn entry_is_new(&self) -> bool {
335 self.old_size_bytes == 0 && self.old_live_until_ledger == 0
336 }
337
338 fn extension_ledgers(&self, current_ledger: u32) -> Option<u32> {
339 let ledger_before_extension = if self.entry_is_new() {
340 current_ledger.saturating_sub(1)
341 } else {
342 self.old_live_until_ledger
343 };
344 exclusive_ledger_diff(ledger_before_extension, self.new_live_until_ledger)
345 }
346
347 fn prepaid_ledgers(&self, current_ledger: u32) -> Option<u32> {
348 if self.entry_is_new() {
349 None
350 } else {
351 inclusive_ledger_diff(current_ledger, self.old_live_until_ledger)
352 }
353 }
354
355 fn size_increase(&self) -> Option<u32> {
356 self.new_size_bytes.checked_sub(self.old_size_bytes)
357 }
358}
359
360fn rent_fee_per_entry_change(
361 entry_change: &LedgerEntryRentChange,
362 fee_config: &RentFeeConfiguration,
363 current_ledger: u32,
364) -> i64 {
365 let mut fee: i64 = 0;
366 if let Some(rent_ledgers) = entry_change.extension_ledgers(current_ledger) {
369 fee = fee.saturating_add(rent_fee_for_size_and_ledgers(
370 entry_change.is_persistent,
371 entry_change.new_size_bytes,
372 rent_ledgers,
373 fee_config,
374 ));
375 }
376
377 if let (Some(rent_ledgers), Some(entry_size)) = (
381 entry_change.prepaid_ledgers(current_ledger),
382 entry_change.size_increase(),
383 ) {
384 fee = fee.saturating_add(rent_fee_for_size_and_ledgers(
385 entry_change.is_persistent,
386 entry_size,
387 rent_ledgers,
388 fee_config,
389 ));
390 }
391 if entry_change.is_code_entry {
392 fee /= CODE_ENTRY_RENT_DISCOUNT_FACTOR;
393 }
394 fee
395}
396
397fn rent_fee_for_size_and_ledgers(
398 is_persistent: bool,
399 entry_size: u32,
400 rent_ledgers: u32,
401 fee_config: &RentFeeConfiguration,
402) -> i64 {
403 let num = (entry_size as i64)
407 .saturating_mul(fee_config.fee_per_rent_1kb)
408 .saturating_mul(rent_ledgers as i64);
409 let storage_coef = if is_persistent {
410 fee_config.persistent_rent_rate_denominator
411 } else {
412 fee_config.temporary_rent_rate_denominator
413 };
414 let denom = DATA_SIZE_1KB_INCREMENT.saturating_mul(storage_coef);
415 num_integer::div_ceil(num, denom.max(1))
416}
417
418fn compute_fee_per_increment(resource_value: u32, fee_rate: i64, increment: i64) -> i64 {
419 let resource_val: i64 = resource_value.into();
420 num_integer::div_ceil(resource_val.saturating_mul(fee_rate), increment.max(1))
421}