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_WRITE_FEE_PER_1KB: i64 = 1000;
18
19pub struct TransactionResources {
21 pub instructions: u32,
23 pub read_entries: u32,
25 pub write_entries: u32,
28 pub read_bytes: u32,
30 pub write_bytes: u32,
32 pub contract_events_size_bytes: u32,
34 pub transaction_size_bytes: u32,
36}
37
38#[derive(Debug, Default, PartialEq, Eq)]
45pub struct FeeConfiguration {
46 pub fee_per_instruction_increment: i64,
48 pub fee_per_read_entry: i64,
50 pub fee_per_write_entry: i64,
52 pub fee_per_read_1kb: i64,
54 pub fee_per_write_1kb: i64,
57 pub fee_per_historical_1kb: i64,
60 pub fee_per_contract_event_1kb: i64,
62 pub fee_per_transaction_size_1kb: i64,
64}
65
66#[derive(Debug, Default, PartialEq, Eq)]
70pub struct WriteFeeConfiguration {
71 pub bucket_list_target_size_bytes: i64,
73 pub write_fee_1kb_bucket_list_low: i64,
75 pub write_fee_1kb_bucket_list_high: i64,
78 pub bucket_list_write_fee_growth_factor: u32,
81}
82
83pub struct LedgerEntryRentChange {
89 pub is_persistent: bool,
91 pub old_size_bytes: u32,
95 pub new_size_bytes: u32,
98 pub old_live_until_ledger: u32,
101 pub new_live_until_ledger: u32,
103}
104
105#[derive(Debug, Default, PartialEq, Eq)]
112pub struct RentFeeConfiguration {
113 pub fee_per_write_1kb: i64,
117 pub fee_per_write_entry: i64,
120 pub persistent_rent_rate_denominator: i64,
127 pub temporary_rent_rate_denominator: i64,
131}
132
133pub fn compute_transaction_resource_fee(
142 tx_resources: &TransactionResources,
143 fee_config: &FeeConfiguration,
144) -> (i64, i64) {
145 let compute_fee = compute_fee_per_increment(
146 tx_resources.instructions,
147 fee_config.fee_per_instruction_increment,
148 INSTRUCTIONS_INCREMENT,
149 );
150 let ledger_read_entry_fee: i64 = fee_config.fee_per_read_entry.saturating_mul(
151 tx_resources
152 .read_entries
153 .saturating_add(tx_resources.write_entries)
154 .into(),
155 );
156 let ledger_write_entry_fee = fee_config
157 .fee_per_write_entry
158 .saturating_mul(tx_resources.write_entries.into());
159 let ledger_read_bytes_fee = compute_fee_per_increment(
160 tx_resources.read_bytes,
161 fee_config.fee_per_read_1kb,
162 DATA_SIZE_1KB_INCREMENT,
163 );
164 let ledger_write_bytes_fee = compute_fee_per_increment(
165 tx_resources.write_bytes,
166 fee_config.fee_per_write_1kb,
167 DATA_SIZE_1KB_INCREMENT,
168 );
169
170 let historical_fee = compute_fee_per_increment(
171 tx_resources
172 .transaction_size_bytes
173 .saturating_add(TX_BASE_RESULT_SIZE),
174 fee_config.fee_per_historical_1kb,
175 DATA_SIZE_1KB_INCREMENT,
176 );
177
178 let events_fee = compute_fee_per_increment(
179 tx_resources.contract_events_size_bytes,
180 fee_config.fee_per_contract_event_1kb,
181 DATA_SIZE_1KB_INCREMENT,
182 );
183
184 let bandwidth_fee = compute_fee_per_increment(
185 tx_resources.transaction_size_bytes,
186 fee_config.fee_per_transaction_size_1kb,
187 DATA_SIZE_1KB_INCREMENT,
188 );
189
190 let refundable_fee = events_fee;
191 let non_refundable_fee = compute_fee
192 .saturating_add(ledger_read_entry_fee)
193 .saturating_add(ledger_write_entry_fee)
194 .saturating_add(ledger_read_bytes_fee)
195 .saturating_add(ledger_write_bytes_fee)
196 .saturating_add(historical_fee)
197 .saturating_add(bandwidth_fee);
198
199 (non_refundable_fee, refundable_fee)
200}
201
202trait ClampFee {
205 fn clamp_fee(self) -> i64;
206}
207
208impl ClampFee for i64 {
209 fn clamp_fee(self) -> i64 {
210 if self < 0 {
211 i64::MAX
218 } else {
219 self
220 }
221 }
222}
223
224impl ClampFee for i128 {
225 fn clamp_fee(self) -> i64 {
226 if self < 0 {
227 i64::MAX
228 } else {
229 i64::try_from(self).unwrap_or(i64::MAX)
230 }
231 }
232}
233
234pub fn compute_write_fee_per_1kb(
242 bucket_list_size_bytes: i64,
243 fee_config: &WriteFeeConfiguration,
244) -> i64 {
245 let fee_rate_multiplier = fee_config
246 .write_fee_1kb_bucket_list_high
247 .saturating_sub(fee_config.write_fee_1kb_bucket_list_low)
248 .clamp_fee();
249 let mut write_fee_per_1kb: i64;
250 if bucket_list_size_bytes < fee_config.bucket_list_target_size_bytes {
251 write_fee_per_1kb = num_integer::div_ceil(
254 (fee_rate_multiplier as i128).saturating_mul(bucket_list_size_bytes as i128),
255 (fee_config.bucket_list_target_size_bytes as i128).max(1),
256 )
257 .clamp_fee();
258 write_fee_per_1kb =
260 write_fee_per_1kb.saturating_add(fee_config.write_fee_1kb_bucket_list_low);
261 } else {
262 write_fee_per_1kb = fee_config.write_fee_1kb_bucket_list_high;
263 let bucket_list_size_after_reaching_target =
264 bucket_list_size_bytes.saturating_sub(fee_config.bucket_list_target_size_bytes);
265 let post_target_fee = num_integer::div_ceil(
266 (fee_rate_multiplier as i128)
267 .saturating_mul(bucket_list_size_after_reaching_target as i128)
268 .saturating_mul(fee_config.bucket_list_write_fee_growth_factor as i128),
269 (fee_config.bucket_list_target_size_bytes as i128).max(1),
270 )
271 .clamp_fee();
272 write_fee_per_1kb = write_fee_per_1kb.saturating_add(post_target_fee);
273 }
274
275 write_fee_per_1kb.max(MINIMUM_WRITE_FEE_PER_1KB)
276}
277
278pub fn compute_rent_fee(
287 changed_entries: &[LedgerEntryRentChange],
288 fee_config: &RentFeeConfiguration,
289 current_ledger_seq: u32,
290) -> i64 {
291 let mut fee: i64 = 0;
292 let mut extended_entries: i64 = 0;
293 let mut extended_entry_key_size_bytes: u32 = 0;
294 for e in changed_entries {
295 fee = fee.saturating_add(rent_fee_per_entry_change(e, fee_config, current_ledger_seq));
296 if e.old_live_until_ledger < e.new_live_until_ledger {
297 extended_entries = extended_entries.saturating_add(1);
298 extended_entry_key_size_bytes =
299 extended_entry_key_size_bytes.saturating_add(TTL_ENTRY_SIZE);
300 }
301 }
302 fee = fee.saturating_add(
306 fee_config
307 .fee_per_write_entry
308 .saturating_mul(extended_entries),
309 );
310 fee = fee.saturating_add(compute_fee_per_increment(
311 extended_entry_key_size_bytes,
312 fee_config.fee_per_write_1kb,
313 DATA_SIZE_1KB_INCREMENT,
314 ));
315
316 fee
317}
318
319fn exclusive_ledger_diff(lo: u32, hi: u32) -> Option<u32> {
321 hi.checked_sub(lo)
322}
323
324fn inclusive_ledger_diff(lo: u32, hi: u32) -> Option<u32> {
326 exclusive_ledger_diff(lo, hi).map(|diff| diff.saturating_add(1))
327}
328
329impl LedgerEntryRentChange {
330 fn entry_is_new(&self) -> bool {
331 self.old_size_bytes == 0 && self.old_live_until_ledger == 0
332 }
333
334 fn extension_ledgers(&self, current_ledger: u32) -> Option<u32> {
335 let ledger_before_extension = if self.entry_is_new() {
336 current_ledger.saturating_sub(1)
337 } else {
338 self.old_live_until_ledger
339 };
340 exclusive_ledger_diff(ledger_before_extension, self.new_live_until_ledger)
341 }
342
343 fn prepaid_ledgers(&self, current_ledger: u32) -> Option<u32> {
344 if self.entry_is_new() {
345 None
346 } else {
347 inclusive_ledger_diff(current_ledger, self.old_live_until_ledger)
348 }
349 }
350
351 fn size_increase(&self) -> Option<u32> {
352 self.new_size_bytes.checked_sub(self.old_size_bytes)
353 }
354}
355
356fn rent_fee_per_entry_change(
357 entry_change: &LedgerEntryRentChange,
358 fee_config: &RentFeeConfiguration,
359 current_ledger: u32,
360) -> i64 {
361 let mut fee: i64 = 0;
362 if let Some(rent_ledgers) = entry_change.extension_ledgers(current_ledger) {
365 fee = fee.saturating_add(rent_fee_for_size_and_ledgers(
366 entry_change.is_persistent,
367 entry_change.new_size_bytes,
368 rent_ledgers,
369 fee_config,
370 ));
371 }
372
373 if let (Some(rent_ledgers), Some(entry_size)) = (
377 entry_change.prepaid_ledgers(current_ledger),
378 entry_change.size_increase(),
379 ) {
380 fee = fee.saturating_add(rent_fee_for_size_and_ledgers(
381 entry_change.is_persistent,
382 entry_size,
383 rent_ledgers,
384 fee_config,
385 ));
386 }
387 fee
388}
389
390fn rent_fee_for_size_and_ledgers(
391 is_persistent: bool,
392 entry_size: u32,
393 rent_ledgers: u32,
394 fee_config: &RentFeeConfiguration,
395) -> i64 {
396 let num = (entry_size as i64)
400 .saturating_mul(fee_config.fee_per_write_1kb)
401 .saturating_mul(rent_ledgers as i64);
402 let storage_coef = if is_persistent {
403 fee_config.persistent_rent_rate_denominator
404 } else {
405 fee_config.temporary_rent_rate_denominator
406 };
407 let denom = DATA_SIZE_1KB_INCREMENT.saturating_mul(storage_coef);
408 num_integer::div_ceil(num, denom.max(1))
409}
410
411fn compute_fee_per_increment(resource_value: u32, fee_rate: i64, increment: i64) -> i64 {
412 let resource_val: i64 = resource_value.into();
413 num_integer::div_ceil(resource_val.saturating_mul(fee_rate), increment.max(1))
414}