1use serde::{Deserialize, Serialize};
2
3pub const TX_BASE_RESULT_SIZE: u32 = 300;
12pub const TTL_ENTRY_SIZE: u32 = 48;
14
15const INSTRUCTIONS_INCREMENT: i64 = 10000;
16const DATA_SIZE_1KB_INCREMENT: i64 = 1024;
17
18pub const MINIMUM_WRITE_FEE_PER_1KB: i64 = 1000;
20
21pub struct TransactionResources {
23 pub instructions: u32,
25 pub read_entries: u32,
27 pub write_entries: u32,
30 pub read_bytes: u32,
32 pub write_bytes: u32,
34 pub contract_events_size_bytes: u32,
36 pub transaction_size_bytes: u32,
38}
39
40#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize, Clone)]
47pub struct FeeConfiguration {
48 pub fee_per_instruction_increment: i64,
50 pub fee_per_read_entry: i64,
52 pub fee_per_write_entry: i64,
54 pub fee_per_read_1kb: i64,
56 pub fee_per_write_1kb: i64,
59 pub fee_per_historical_1kb: i64,
62 pub fee_per_contract_event_1kb: i64,
64 pub fee_per_transaction_size_1kb: i64,
66}
67
68#[derive(Debug, Default, PartialEq, Eq)]
72pub struct WriteFeeConfiguration {
73 pub bucket_list_target_size_bytes: i64,
75 pub write_fee_1kb_bucket_list_low: i64,
77 pub write_fee_1kb_bucket_list_high: i64,
80 pub bucket_list_write_fee_growth_factor: u32,
83}
84
85pub struct LedgerEntryRentChange {
91 pub is_persistent: bool,
93 pub old_size_bytes: u32,
97 pub new_size_bytes: u32,
100 pub old_live_until_ledger: u32,
103 pub new_live_until_ledger: u32,
105}
106
107#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize, Clone)]
114pub struct RentFeeConfiguration {
115 pub fee_per_write_1kb: i64,
119 pub fee_per_write_entry: i64,
122 pub persistent_rent_rate_denominator: i64,
129 pub temporary_rent_rate_denominator: i64,
133}
134
135pub fn compute_transaction_resource_fee(
144 tx_resources: &TransactionResources,
145 fee_config: &FeeConfiguration,
146) -> (i64, i64) {
147 let compute_fee = compute_fee_per_increment(
148 tx_resources.instructions,
149 fee_config.fee_per_instruction_increment,
150 INSTRUCTIONS_INCREMENT,
151 );
152 let ledger_read_entry_fee: i64 = fee_config.fee_per_read_entry.saturating_mul(
153 tx_resources
154 .read_entries
155 .saturating_add(tx_resources.write_entries)
156 .into(),
157 );
158 let ledger_write_entry_fee = fee_config
159 .fee_per_write_entry
160 .saturating_mul(tx_resources.write_entries.into());
161 let ledger_read_bytes_fee = compute_fee_per_increment(
162 tx_resources.read_bytes,
163 fee_config.fee_per_read_1kb,
164 DATA_SIZE_1KB_INCREMENT,
165 );
166 let ledger_write_bytes_fee = compute_fee_per_increment(
167 tx_resources.write_bytes,
168 fee_config.fee_per_write_1kb,
169 DATA_SIZE_1KB_INCREMENT,
170 );
171
172 let historical_fee = compute_fee_per_increment(
173 tx_resources
174 .transaction_size_bytes
175 .saturating_add(TX_BASE_RESULT_SIZE),
176 fee_config.fee_per_historical_1kb,
177 DATA_SIZE_1KB_INCREMENT,
178 );
179
180 let events_fee = compute_fee_per_increment(
181 tx_resources.contract_events_size_bytes,
182 fee_config.fee_per_contract_event_1kb,
183 DATA_SIZE_1KB_INCREMENT,
184 );
185
186 let bandwidth_fee = compute_fee_per_increment(
187 tx_resources.transaction_size_bytes,
188 fee_config.fee_per_transaction_size_1kb,
189 DATA_SIZE_1KB_INCREMENT,
190 );
191
192 let refundable_fee = events_fee;
193 let non_refundable_fee = compute_fee
194 .saturating_add(ledger_read_entry_fee)
195 .saturating_add(ledger_write_entry_fee)
196 .saturating_add(ledger_read_bytes_fee)
197 .saturating_add(ledger_write_bytes_fee)
198 .saturating_add(historical_fee)
199 .saturating_add(bandwidth_fee);
200
201 (non_refundable_fee, refundable_fee)
202}
203
204trait ClampFee {
207 fn clamp_fee(self) -> i64;
208}
209
210impl ClampFee for i64 {
211 fn clamp_fee(self) -> i64 {
212 if self < 0 {
213 i64::MAX
220 } else {
221 self
222 }
223 }
224}
225
226impl ClampFee for i128 {
227 fn clamp_fee(self) -> i64 {
228 if self < 0 {
229 i64::MAX
230 } else {
231 i64::try_from(self).unwrap_or(i64::MAX)
232 }
233 }
234}
235
236pub fn compute_write_fee_per_1kb(
244 bucket_list_size_bytes: i64,
245 fee_config: &WriteFeeConfiguration,
246) -> i64 {
247 let fee_rate_multiplier = fee_config
248 .write_fee_1kb_bucket_list_high
249 .saturating_sub(fee_config.write_fee_1kb_bucket_list_low)
250 .clamp_fee();
251 let mut write_fee_per_1kb: i64;
252 if bucket_list_size_bytes < fee_config.bucket_list_target_size_bytes {
253 write_fee_per_1kb = num_integer::div_ceil(
256 (fee_rate_multiplier as i128).saturating_mul(bucket_list_size_bytes as i128),
257 (fee_config.bucket_list_target_size_bytes as i128).max(1),
258 )
259 .clamp_fee();
260 write_fee_per_1kb =
262 write_fee_per_1kb.saturating_add(fee_config.write_fee_1kb_bucket_list_low);
263 } else {
264 write_fee_per_1kb = fee_config.write_fee_1kb_bucket_list_high;
265 let bucket_list_size_after_reaching_target =
266 bucket_list_size_bytes.saturating_sub(fee_config.bucket_list_target_size_bytes);
267 let post_target_fee = num_integer::div_ceil(
268 (fee_rate_multiplier as i128)
269 .saturating_mul(bucket_list_size_after_reaching_target as i128)
270 .saturating_mul(fee_config.bucket_list_write_fee_growth_factor as i128),
271 (fee_config.bucket_list_target_size_bytes as i128).max(1),
272 )
273 .clamp_fee();
274 write_fee_per_1kb = write_fee_per_1kb.saturating_add(post_target_fee);
275 }
276
277 write_fee_per_1kb.max(MINIMUM_WRITE_FEE_PER_1KB)
278}
279
280pub fn compute_rent_fee(
289 changed_entries: &[LedgerEntryRentChange],
290 fee_config: &RentFeeConfiguration,
291 current_ledger_seq: u32,
292) -> i64 {
293 let mut fee: i64 = 0;
294 let mut extended_entries: i64 = 0;
295 let mut extended_entry_key_size_bytes: u32 = 0;
296 for e in changed_entries {
297 fee = fee.saturating_add(rent_fee_per_entry_change(e, fee_config, current_ledger_seq));
298 if e.old_live_until_ledger < e.new_live_until_ledger {
299 extended_entries = extended_entries.saturating_add(1);
300 extended_entry_key_size_bytes =
301 extended_entry_key_size_bytes.saturating_add(TTL_ENTRY_SIZE);
302 }
303 }
304 fee = fee.saturating_add(
308 fee_config
309 .fee_per_write_entry
310 .saturating_mul(extended_entries),
311 );
312 fee = fee.saturating_add(compute_fee_per_increment(
313 extended_entry_key_size_bytes,
314 fee_config.fee_per_write_1kb,
315 DATA_SIZE_1KB_INCREMENT,
316 ));
317
318 fee
319}
320
321fn exclusive_ledger_diff(lo: u32, hi: u32) -> Option<u32> {
323 hi.checked_sub(lo)
324}
325
326fn inclusive_ledger_diff(lo: u32, hi: u32) -> Option<u32> {
328 exclusive_ledger_diff(lo, hi).map(|diff| diff.saturating_add(1))
329}
330
331impl LedgerEntryRentChange {
332 fn entry_is_new(&self) -> bool {
333 self.old_size_bytes == 0 && self.old_live_until_ledger == 0
334 }
335
336 fn extension_ledgers(&self, current_ledger: u32) -> Option<u32> {
337 let ledger_before_extension = if self.entry_is_new() {
338 current_ledger.saturating_sub(1)
339 } else {
340 self.old_live_until_ledger
341 };
342 exclusive_ledger_diff(ledger_before_extension, self.new_live_until_ledger)
343 }
344
345 fn prepaid_ledgers(&self, current_ledger: u32) -> Option<u32> {
346 if self.entry_is_new() {
347 None
348 } else {
349 inclusive_ledger_diff(current_ledger, self.old_live_until_ledger)
350 }
351 }
352
353 fn size_increase(&self) -> Option<u32> {
354 self.new_size_bytes.checked_sub(self.old_size_bytes)
355 }
356}
357
358fn rent_fee_per_entry_change(
359 entry_change: &LedgerEntryRentChange,
360 fee_config: &RentFeeConfiguration,
361 current_ledger: u32,
362) -> i64 {
363 let mut fee: i64 = 0;
364 if let Some(rent_ledgers) = entry_change.extension_ledgers(current_ledger) {
367 fee = fee.saturating_add(rent_fee_for_size_and_ledgers(
368 entry_change.is_persistent,
369 entry_change.new_size_bytes,
370 rent_ledgers,
371 fee_config,
372 ));
373 }
374
375 if let (Some(rent_ledgers), Some(entry_size)) = (
379 entry_change.prepaid_ledgers(current_ledger),
380 entry_change.size_increase(),
381 ) {
382 fee = fee.saturating_add(rent_fee_for_size_and_ledgers(
383 entry_change.is_persistent,
384 entry_size,
385 rent_ledgers,
386 fee_config,
387 ));
388 }
389 fee
390}
391
392fn rent_fee_for_size_and_ledgers(
393 is_persistent: bool,
394 entry_size: u32,
395 rent_ledgers: u32,
396 fee_config: &RentFeeConfiguration,
397) -> i64 {
398 let num = (entry_size as i64)
402 .saturating_mul(fee_config.fee_per_write_1kb)
403 .saturating_mul(rent_ledgers as i64);
404 let storage_coef = if is_persistent {
405 fee_config.persistent_rent_rate_denominator
406 } else {
407 fee_config.temporary_rent_rate_denominator
408 };
409 let denom = DATA_SIZE_1KB_INCREMENT.saturating_mul(storage_coef);
410 num_integer::div_ceil(num, denom.max(1))
411}
412
413fn compute_fee_per_increment(resource_value: u32, fee_rate: i64, increment: i64) -> i64 {
414 let resource_val: i64 = resource_value.into();
415 num_integer::div_ceil(resource_val.saturating_mul(fee_rate), increment.max(1))
416}