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}