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