Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 25 additions & 6 deletions applications/tari_ootle_app_utilities/src/fee_tables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@
//! - **`per_byte_storage_cost`**: Cost per byte of data written to persistent storage (substates). Note: The actual
//! cost is reduced by a factor of 4 (cost × 0.25) to make storage more affordable.
//!
//! ## Template Publishing Fees
//!
//! A published template's binary is priced by its own model instead of the flat per-byte storage rate, since it is
//! the largest single payload a caller can commit to permanent state:
//!
//! - `per_template_publish_cost` — flat, charged once per publish.
//! - `template_size_premium_free_bytes` — priced at `per_byte_storage_cost`, no premium.
//! - `per_template_size_premium_unit_cost × units²` — where `units = (size − free) / template_size_premium_unit_bytes`.
//!
//! The quadratic term is what makes oversized templates expensive; the flat term sets the floor, because a small
//! template's size is dominated by fixed `template_lib` machinery rather than by anything its author wrote.
//!
//! ## Cryptographic Operation Fees
//!
//! - **`per_signature_verification_cost`**: Cost for each cryptographic signature verification performed during
Expand Down Expand Up @@ -81,14 +93,20 @@ const TESTNET_FEE_TABLE: FeeTable = FeeTable {
template_load_bytes_cost_divisor: 3000,
// 1 µT per 1000 Wasmer points. Lower values make metering more aggressive.
wasm_points_cost_divisor: 1000,
// First 30 KiB of a template binary are priced at the per-byte storage rate; beyond that the
// quadratic publish premium applies. Keeps small/typical templates as cheap as before.
template_size_premium_free_bytes: 30 * 1024,
// First 96 KiB of a template binary are priced at the per-byte storage rate; beyond that the
// quadratic publish premium applies. A published template carries ~25 KiB of fixed
// `template_lib` machinery before any author code, and a minimal component template optimised
// the way the publish path optimises it lands around 76 KiB, so the allowance sits just above
// the floor: the premium prices author content, not library overhead.
template_size_premium_free_bytes: 96 * 1024,
// 1 KiB per premium unit.
template_size_premium_unit_bytes: 1024,
// 100 µT per unit². e.g. a 64 KiB template (34 units over the free allowance) pays
// 34² × 100 ≈ 0.12 tTARI premium; a 512 KiB template pays ≈ 23.2 tTARI.
// 100 µT per unit². e.g. a 256 KiB template (160 units over the free allowance) pays
// 160² × 100 ≈ 2.6 tTARI premium; a 512 KiB template pays ≈ 17.3 tTARI.
per_template_size_premium_unit_cost: 100,
// 250_000 µT flat per publish. The size premium alone cannot price a small template above the
// noise floor, since most of a small binary is library overhead rather than author content.
per_template_publish_cost: 250_000,
};

/// MainNet fee table - production values.
Expand All @@ -115,9 +133,10 @@ const MAINNET_FEE_TABLE: FeeTable = FeeTable {
storage_cost_divisor: 1,
template_load_bytes_cost_divisor: 3000,
wasm_points_cost_divisor: 1000,
template_size_premium_free_bytes: 30 * 1024,
template_size_premium_free_bytes: 96 * 1024,
template_size_premium_unit_bytes: 1024,
per_template_size_premium_unit_cost: 100,
per_template_publish_cost: 250_000,
};

/// Returns the appropriate fee table for the specified network.
Expand Down
50 changes: 29 additions & 21 deletions crates/engine/src/fees/fee_module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,30 +24,32 @@ impl FeeModule {
}
}

/// Raw storage metrics for persisting a published template's `binary_len`-byte binary: the
/// bytes priced at the per-byte storage rate (the first `template_size_premium_free_bytes`, so
/// small templates cost the same as ordinary storage) and the quadratic premium on each whole
/// unit of the excess above that. Returned raw — not pre-divided — so the caller can accumulate
/// across the transaction and apply `storage_cost_divisor` once at finalization, matching how
/// the other byte/point charges round against per-transaction totals.
/// Metrics for persisting a published template's `binary_len`-byte binary: the bytes priced at
/// the per-byte storage rate (the first `template_size_premium_free_bytes`, so small templates
/// cost the same as ordinary storage), and the microtari charged on top — the flat per-publish
/// cost plus the quadratic premium on each whole unit of the excess above the free allowance.
/// The byte count is returned raw — not pre-divided — so the caller can accumulate across the
/// transaction and apply `storage_cost_divisor` once at finalization, matching how the other
/// byte/point charges round against per-transaction totals.
fn template_publish_metrics(&self, binary_len: usize) -> Result<(u64, u64), RuntimeModuleError> {
let size = binary_len as u64;
let free = self.fee_table.template_size_premium_free_bytes();

let base_bytes = size.min(free);

let units = size.saturating_sub(free) / self.fee_table.template_size_premium_unit_bytes();
let premium = units
let charge = units
.checked_mul(units)
.and_then(|squared| squared.checked_mul(self.fee_table.per_template_size_premium_unit_cost()))
.and_then(|premium| premium.checked_add(self.fee_table.per_template_publish_cost()))
.ok_or_else(|| RuntimeModuleError::Overflow("Overflow calculating template publish premium".to_string()))?;

Ok((base_bytes, premium))
Ok((base_bytes, charge))
}

#[cfg(test)]
fn template_publish_cost(&self, binary_len: usize) -> Result<u64, RuntimeModuleError> {
let (base_bytes, premium) = self.template_publish_metrics(binary_len)?;
let (base_bytes, charge) = self.template_publish_metrics(binary_len)?;
let base = self
.fee_table
.per_byte_storage_cost()
Expand All @@ -56,7 +58,7 @@ impl FeeModule {
RuntimeModuleError::Overflow("Overflow calculating template base storage cost".to_string())
})? /
self.fee_table.storage_cost_divisor();
base.checked_add(premium)
base.checked_add(charge)
.ok_or_else(|| RuntimeModuleError::Overflow("Overflow calculating template publish cost".to_string()))
}
}
Expand Down Expand Up @@ -108,27 +110,27 @@ impl<TStore: StateReader> RuntimeModule<TStore> for FeeModule {
}

fn on_before_finalize(&self, track: &mut StateTracker<TStore>) -> Result<(), RuntimeModuleError> {
let (total_storage, template_base_bytes, template_premium) = track.with_substates_to_persist(|changes| {
let (total_storage, template_base_bytes, template_charge) = track.with_substates_to_persist(|changes| {
let mut counter = ByteCounter::new();
let mut base_bytes = 0u64;
let mut premium = 0u64;
let mut charge = 0u64;
for substate in changes.values() {
// A published template's binary is priced by the dedicated base + quadratic publish
// model, so keep it out of the flat per-byte storage tally. Accumulate the raw
// metrics here and apply the storage divisor once below.
if let SubstateValue::Template(template) = substate {
let (tpl_base_bytes, tpl_premium) = self.template_publish_metrics(template.binary.len())?;
let (tpl_base_bytes, tpl_charge) = self.template_publish_metrics(template.binary.len())?;
base_bytes = base_bytes.checked_add(tpl_base_bytes).ok_or_else(|| {
RuntimeModuleError::Overflow("Overflow accumulating template base bytes".to_string())
})?;
premium = premium.checked_add(tpl_premium).ok_or_else(|| {
charge = charge.checked_add(tpl_charge).ok_or_else(|| {
RuntimeModuleError::Overflow("Overflow accumulating template publish premium".to_string())
})?;
continue;
}
encode_into_writer(substate, &mut counter)?;
}
Ok::<_, RuntimeModuleError>((counter.get(), base_bytes, premium))
Ok::<_, RuntimeModuleError>((counter.get(), base_bytes, charge))
})?;

let cost = self
Expand All @@ -147,7 +149,7 @@ impl<TStore: StateReader> RuntimeModule<TStore> for FeeModule {
})? /
self.fee_table.storage_cost_divisor();
let template_publish_cost = template_base_cost
.checked_add(template_premium)
.checked_add(template_charge)
.ok_or_else(|| RuntimeModuleError::Overflow("Overflow calculating template publish cost".to_string()))?;
if template_publish_cost > 0 {
track.add_fee_charge(FeeSource::TemplatePublish, template_publish_cost);
Expand Down Expand Up @@ -237,6 +239,7 @@ mod tests {
template_size_premium_free_bytes: 30 * 1024,
template_size_premium_unit_bytes: 1024,
per_template_size_premium_unit_cost: 100,
per_template_publish_cost: 500,
..FeeTable::zero_rated()
}
}
Expand All @@ -248,16 +251,21 @@ mod tests {
}

#[test]
fn at_or_below_the_free_allowance_is_linear() {
assert_eq!(publish_cost(20 * 1024), 20 * 1024);
assert_eq!(publish_cost(30 * 1024), 30 * 1024);
fn the_flat_cost_is_charged_whatever_the_size() {
assert_eq!(publish_cost(0), 500);
}

#[test]
fn at_or_below_the_free_allowance_is_flat_plus_linear() {
assert_eq!(publish_cost(20 * 1024), 500 + 20 * 1024);
assert_eq!(publish_cost(30 * 1024), 500 + 30 * 1024);
}

#[test]
fn above_the_free_allowance_adds_a_quadratic_premium() {
// base = first 30 KiB linear; premium = units² × 100, units = excess_KiB.
assert_eq!(publish_cost(38 * 1024), 30 * 1024 + 8 * 8 * 100);
assert_eq!(publish_cost(64 * 1024), 30 * 1024 + 34 * 34 * 100);
assert_eq!(publish_cost(38 * 1024), 500 + 30 * 1024 + 8 * 8 * 100);
assert_eq!(publish_cost(64 * 1024), 500 + 30 * 1024 + 34 * 34 * 100);
}

#[test]
Expand Down
10 changes: 10 additions & 0 deletions crates/engine/src/fees/fee_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ pub struct FeeTable {
/// (`per_template_size_premium_unit_cost × units²`, `units = excess_bytes /
/// template_size_premium_unit_bytes`). Set to 0 to disable the publish premium.
pub per_template_size_premium_unit_cost: u64,
/// Flat cost charged once per published template, independent of its size. A template's binary
/// is mostly fixed `template_lib` overhead rather than author content, so size alone cannot
/// price a small publish above the noise floor; this sets that floor. Set to 0 to charge for
/// size only.
pub per_template_publish_cost: u64,
}

impl FeeTable {
Expand All @@ -58,6 +63,7 @@ impl FeeTable {
template_size_premium_free_bytes: 0,
template_size_premium_unit_bytes: 1,
per_template_size_premium_unit_cost: 0,
per_template_publish_cost: 0,
}
}

Expand Down Expand Up @@ -112,6 +118,10 @@ impl FeeTable {
pub fn per_template_size_premium_unit_cost(&self) -> u64 {
self.per_template_size_premium_unit_cost
}

pub fn per_template_publish_cost(&self) -> u64 {
self.per_template_publish_cost
}
}

fn non_zero(divisor: u64) -> u64 {
Expand Down
3 changes: 2 additions & 1 deletion crates/template_test_tooling/src/template_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,9 +249,10 @@ impl TemplateTest {
storage_cost_divisor: 1,
template_load_bytes_cost_divisor: 3000,
wasm_points_cost_divisor: 1000,
template_size_premium_free_bytes: 30 * 1024,
template_size_premium_free_bytes: 96 * 1024,
template_size_premium_unit_bytes: 1024,
per_template_size_premium_unit_cost: 100,
per_template_publish_cost: 250_000,
},
key_seed: 1,
auto_add_proofs_from_signers: true,
Expand Down
Loading