use alloc::vec::Vec;
use core::cmp::max;
use ::transparent::bundle::OutPoint;
use zcash_protocol::{
consensus::{self, BlockHeight},
value::{BalanceError, Zatoshis},
};
use crate::transaction::fees::transparent;
pub const MARGINAL_FEE: Zatoshis = Zatoshis::const_from_u64(5_000);
pub const GRACE_ACTIONS: usize = 2;
pub const P2PKH_STANDARD_INPUT_SIZE: usize = 150;
pub const P2PKH_STANDARD_OUTPUT_SIZE: usize = 34;
pub const MINIMUM_FEE: Zatoshis = Zatoshis::const_from_u64(10_000);
#[derive(Clone, Debug)]
pub struct FeeRule {
marginal_fee: Zatoshis,
grace_actions: usize,
p2pkh_standard_input_size: usize,
p2pkh_standard_output_size: usize,
}
impl FeeRule {
pub fn standard() -> Self {
Self {
marginal_fee: MARGINAL_FEE,
grace_actions: GRACE_ACTIONS,
p2pkh_standard_input_size: P2PKH_STANDARD_INPUT_SIZE,
p2pkh_standard_output_size: P2PKH_STANDARD_OUTPUT_SIZE,
}
}
#[cfg(feature = "non-standard-fees")]
pub fn non_standard(
marginal_fee: Zatoshis,
grace_actions: usize,
p2pkh_standard_input_size: usize,
p2pkh_standard_output_size: usize,
) -> Option<Self> {
if p2pkh_standard_input_size == 0 || p2pkh_standard_output_size == 0 {
None
} else {
Some(Self {
marginal_fee,
grace_actions,
p2pkh_standard_input_size,
p2pkh_standard_output_size,
})
}
}
pub fn marginal_fee(&self) -> Zatoshis {
self.marginal_fee
}
pub fn grace_actions(&self) -> usize {
self.grace_actions
}
pub fn p2pkh_standard_input_size(&self) -> usize {
self.p2pkh_standard_input_size
}
pub fn p2pkh_standard_output_size(&self) -> usize {
self.p2pkh_standard_output_size
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FeeError {
Balance(BalanceError),
UnknownP2shInputs(Vec<OutPoint>),
}
impl From<BalanceError> for FeeError {
fn from(err: BalanceError) -> Self {
FeeError::Balance(err)
}
}
impl core::fmt::Display for FeeError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match &self {
FeeError::Balance(e) => write!(
f,
"A balance calculation violated amount validity bounds: {e}."
),
FeeError::UnknownP2shInputs(_) => {
write!(f, "Only P2PKH or known-P2SH inputs are supported.")
}
}
}
}
impl super::FeeRule for FeeRule {
type Error = FeeError;
fn fee_required<P: consensus::Parameters>(
&self,
_params: &P,
_target_height: BlockHeight,
transparent_input_sizes: impl IntoIterator<Item = transparent::InputSize>,
transparent_output_sizes: impl IntoIterator<Item = usize>,
sapling_input_count: usize,
sapling_output_count: usize,
orchard_action_count: usize,
) -> Result<Zatoshis, Self::Error> {
let mut t_in_total_size: usize = 0;
let mut unknown_p2sh_outpoints = vec![];
for sz in transparent_input_sizes.into_iter() {
match sz {
transparent::InputSize::Known(s) => {
t_in_total_size += s;
}
transparent::InputSize::Unknown(outpoint) => {
unknown_p2sh_outpoints.push(outpoint.clone());
}
}
}
if !unknown_p2sh_outpoints.is_empty() {
return Err(FeeError::UnknownP2shInputs(unknown_p2sh_outpoints));
}
let t_out_total_size = transparent_output_sizes.into_iter().sum();
let ceildiv = |num: usize, den: usize| num.div_ceil(den);
let logical_actions = max(
ceildiv(t_in_total_size, self.p2pkh_standard_input_size),
ceildiv(t_out_total_size, self.p2pkh_standard_output_size),
) + max(sapling_input_count, sapling_output_count)
+ orchard_action_count;
(self.marginal_fee * max(self.grace_actions, logical_actions))
.ok_or_else(|| BalanceError::Overflow.into())
}
}