Skip to main content

dpp/shielded/compute_minimum_shielded_fee/
mod.rs

1mod v0;
2
3use crate::fee::Credits;
4use crate::ProtocolError;
5use platform_version::version::PlatformVersion;
6use v0::compute_minimum_shielded_fee_v0;
7use v0::compute_shielded_unshield_fee_v0;
8use v0::compute_shielded_verification_fee_v0;
9use v0::compute_shielded_withdrawal_fee_v0;
10
11/// Computes the minimum **flat** fee (in credits) for a pool-paid / asset-lock shielded
12/// transition.
13///
14/// Dispatches on the platform-versioned `dpp.methods.compute_minimum_shielded_fee` so the
15/// fee formula can evolve across protocol versions without breaking older ones.
16///
17/// This is the **base** flat shielded fee for the pool-paid / asset-lock transitions whose storage
18/// cannot be metered against an address balance. ShieldedTransfer and ShieldFromAssetLock charge
19/// exactly this base (ShieldFromAssetLock adds the asset-lock base cost on the asset-lock side); the
20/// other two pool-paid transitions add one flat per-transition storage component on top of this
21/// base — Unshield via [`compute_shielded_unshield_fee`] (the `AddBalanceToAddress` output write)
22/// and ShieldedWithdrawal via [`compute_shielded_withdrawal_fee`] (the Core withdrawal document).
23/// For each transition, its SDK builder, its transformer (for the fee actually carved from the
24/// pool), and the consensus gate `validate_minimum_shielded_fee` all call the SAME one of these
25/// functions, so the carved fee and the validation threshold can never drift.
26///
27/// The transparent `Shield` is the exception: it meters its note/nullifier storage via GroveDB and
28/// adds only the COMPUTE portion via the sibling [`compute_shielded_verification_fee`] (which carries no
29/// storage term). Both functions dispatch on the SAME version key, so the flat fee and the compute
30/// fee always evolve together and cannot drift.
31///
32/// # Parameters
33/// - `num_actions` — number of Orchard actions in the bundle
34/// - `platform_version` — protocol version (determines the formula version and fee constants)
35pub fn compute_minimum_shielded_fee(
36    num_actions: usize,
37    platform_version: &PlatformVersion,
38) -> Result<Credits, ProtocolError> {
39    match platform_version.dpp.methods.compute_minimum_shielded_fee {
40        0 => compute_minimum_shielded_fee_v0(num_actions, platform_version),
41        version => Err(ProtocolError::UnknownVersionMismatch {
42            method: "compute_minimum_shielded_fee".to_string(),
43            known_versions: vec![0],
44            received: version,
45        }),
46    }
47}
48
49/// Computes the **ShieldedWithdrawal** fee (in credits): [`compute_minimum_shielded_fee`] PLUS the
50/// flat storage cost of the Core withdrawal document a `ShieldedWithdrawal` inserts.
51///
52/// A `ShieldedWithdrawal` additionally writes a Core withdrawal document into the withdrawals
53/// contract (the document plus its index entries — `AddWithdrawalDocument`), a real,
54/// GroveDB-metered insert (≈110M credits, FLAT regardless of action count) that
55/// [`compute_minimum_shielded_fee`] does NOT price. This function adds that document cost as a
56/// flat `SHIELDED_WITHDRAWAL_DOCUMENT_STORAGE_BYTES`-byte storage component (priced at the same
57/// per-byte storage rate the per-action note storage uses).
58///
59/// Used ONLY by `ShieldedWithdrawal`: its SDK builder, the withdrawal transformer (for the fee
60/// carved from the pool), and the consensus gate `validate_minimum_shielded_fee` all call this
61/// function, so the carved fee and the validation threshold can never drift. ShieldedTransfer keeps
62/// using [`compute_minimum_shielded_fee`], Unshield uses [`compute_shielded_unshield_fee`], and the
63/// entry transitions use [`compute_minimum_shielded_fee`] / [`compute_shielded_verification_fee`].
64///
65/// Dispatches on the SAME version key (`dpp.methods.compute_minimum_shielded_fee`) as
66/// [`compute_minimum_shielded_fee`] so the two formulas evolve together across protocol versions.
67///
68/// # Parameters
69/// - `num_actions` — number of Orchard actions in the bundle
70/// - `platform_version` — protocol version (determines the formula version and fee constants)
71pub fn compute_shielded_withdrawal_fee(
72    num_actions: usize,
73    platform_version: &PlatformVersion,
74) -> Result<Credits, ProtocolError> {
75    match platform_version.dpp.methods.compute_minimum_shielded_fee {
76        0 => compute_shielded_withdrawal_fee_v0(num_actions, platform_version),
77        version => Err(ProtocolError::UnknownVersionMismatch {
78            method: "compute_shielded_withdrawal_fee".to_string(),
79            known_versions: vec![0],
80            received: version,
81        }),
82    }
83}
84
85/// Computes the **Unshield** fee (in credits): [`compute_minimum_shielded_fee`] PLUS the flat
86/// storage cost of the single `AddBalanceToAddress` write an `Unshield` performs.
87///
88/// An `Unshield` additionally credits the net (`unshielding_amount − fee`) to the output platform
89/// address via `AddBalanceToAddress`, a real, GroveDB-metered write (≈6.24M credits, FLAT
90/// regardless of action count) that [`compute_minimum_shielded_fee`] does NOT price. This function
91/// adds that address cost as a flat `SHIELDED_UNSHIELD_ADDRESS_STORAGE_BYTES`-byte storage
92/// component (priced at the same per-byte storage rate the per-action note storage uses).
93///
94/// Used ONLY by `Unshield`: its SDK builder, the unshield transformer (for the fee carved from the
95/// pool), and the consensus gate `validate_minimum_shielded_fee` all call this function, so the
96/// carved fee and the validation threshold can never drift. ShieldedTransfer, ShieldedWithdrawal,
97/// and the entry transitions keep using [`compute_minimum_shielded_fee`] /
98/// [`compute_shielded_withdrawal_fee`] / [`compute_shielded_verification_fee`].
99///
100/// Dispatches on the SAME version key (`dpp.methods.compute_minimum_shielded_fee`) as
101/// [`compute_minimum_shielded_fee`] so the two formulas evolve together across protocol versions.
102///
103/// # Parameters
104/// - `num_actions` — number of Orchard actions in the bundle
105/// - `platform_version` — protocol version (determines the formula version and fee constants)
106pub fn compute_shielded_unshield_fee(
107    num_actions: usize,
108    platform_version: &PlatformVersion,
109) -> Result<Credits, ProtocolError> {
110    match platform_version.dpp.methods.compute_minimum_shielded_fee {
111        0 => compute_shielded_unshield_fee_v0(num_actions, platform_version),
112        version => Err(ProtocolError::UnknownVersionMismatch {
113            method: "compute_shielded_unshield_fee".to_string(),
114            known_versions: vec![0],
115            received: version,
116        }),
117    }
118}
119
120/// Computes the **compute-only** shielded fee (in credits): the ZK-compute portion (Halo 2 proof
121/// verification + per-action spend-auth/nullifier processing) that GroveDB metering cannot see.
122///
123/// Unlike [`compute_minimum_shielded_fee`] this carries **no storage term**. It is used by the
124/// transparent `Shield`, which meters its note/nullifier storage writes via GroveDB and adds only
125/// this compute fee on top (as the event's `additional_fixed_fee_cost`), so storage is never
126/// double-counted.
127///
128/// Dispatches on the SAME version key (`dpp.methods.compute_minimum_shielded_fee`) as
129/// [`compute_minimum_shielded_fee`] so the two formulas evolve together across protocol versions.
130///
131/// # Parameters
132/// - `num_actions` — number of Orchard actions in the bundle
133/// - `platform_version` — protocol version (determines the formula version and fee constants)
134pub fn compute_shielded_verification_fee(
135    num_actions: usize,
136    platform_version: &PlatformVersion,
137) -> Result<Credits, ProtocolError> {
138    match platform_version.dpp.methods.compute_minimum_shielded_fee {
139        0 => compute_shielded_verification_fee_v0(num_actions, platform_version),
140        version => Err(ProtocolError::UnknownVersionMismatch {
141            method: "compute_shielded_verification_fee".to_string(),
142            known_versions: vec![0],
143            received: version,
144        }),
145    }
146}