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