|
@@ -0,0 +1,140 @@
|
|
|
|
|
+/* This file is part of DarkFi (https://dark.fi)
|
|
|
|
|
+ *
|
|
|
|
|
+ * Copyright (C) 2020-2026 Dyne.org foundation
|
|
|
|
|
+ *
|
|
|
|
|
+ * This program is free software: you can redistribute it and/or modify
|
|
|
|
|
+ * it under the terms of the GNU Affero General Public License as
|
|
|
|
|
+ * published by the Free Software Foundation, either version 3 of the
|
|
|
|
|
+ * License, or (at your option) any later version.
|
|
|
|
|
+ *
|
|
|
|
|
+ * This program is distributed in the hope that it will be useful,
|
|
|
|
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
+ * GNU Affero General Public License for more details.
|
|
|
|
|
+ *
|
|
|
|
|
+ * You should have received a copy of the GNU Affero General Public License
|
|
|
|
|
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+use crate::error::{FeeError, FeeResult};
|
|
|
|
|
+
|
|
|
|
|
+/// Fixed consensus fee charged per gas unit for the initial fee-burning testnet.
|
|
|
|
|
+pub const FEE_PER_GAS: u64 = 5;
|
|
|
|
|
+
|
|
|
|
|
+/// Numerator for the mandatory burn ratio applied to the minimum fee.
|
|
|
|
|
+pub const BURN_NUM: u64 = 3;
|
|
|
|
|
+
|
|
|
|
|
+/// Denominator for the mandatory burn ratio applied to the minimum fee.
|
|
|
|
|
+pub const BURN_DEN: u64 = 4;
|
|
|
|
|
+
|
|
|
|
|
+fn validate_fee_constants(fee_per_gas: u64, burn_num: u64, burn_den: u64) -> FeeResult<()> {
|
|
|
|
|
+ if fee_per_gas == 0 || burn_num == 0 || burn_den == 0 || burn_num >= burn_den {
|
|
|
|
|
+ return Err(FeeError::InvalidFeeConstants)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Ok(())
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+fn minimum_fee_with_constants(gas: u64, fee_per_gas: u64) -> FeeResult<u64> {
|
|
|
|
|
+ validate_fee_constants(fee_per_gas, BURN_NUM, BURN_DEN)?;
|
|
|
|
|
+ gas.checked_mul(fee_per_gas).ok_or(FeeError::ArithmeticOverflow)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+fn burn_fee_with_constants(minimum_fee: u64, burn_num: u64, burn_den: u64) -> FeeResult<u64> {
|
|
|
|
|
+ validate_fee_constants(FEE_PER_GAS, burn_num, burn_den)?;
|
|
|
|
|
+
|
|
|
|
|
+ let burned =
|
|
|
|
|
+ (minimum_fee as u128).checked_mul(burn_num as u128).ok_or(FeeError::ArithmeticOverflow)? /
|
|
|
|
|
+ burn_den as u128;
|
|
|
|
|
+
|
|
|
|
|
+ u64::try_from(burned).map_err(|_| FeeError::ArithmeticOverflow)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/// Compute the minimum fee required for final measured gas.
|
|
|
|
|
+pub fn minimum_fee(gas: u64) -> FeeResult<u64> {
|
|
|
|
|
+ minimum_fee_with_constants(gas, FEE_PER_GAS)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/// Compute the mandatory burn for a minimum fee.
|
|
|
|
|
+pub fn burn_fee(minimum_fee: u64) -> FeeResult<u64> {
|
|
|
|
|
+ burn_fee_with_constants(minimum_fee, BURN_NUM, BURN_DEN)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/// Compute the miner-claimable fee from the paid fee and mandatory burn.
|
|
|
|
|
+pub fn miner_claimable_fee(paid_fee: u64, burned_fee: u64) -> FeeResult<u64> {
|
|
|
|
|
+ paid_fee.checked_sub(burned_fee).ok_or(FeeError::InsufficientFee)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/// Checked addition for accumulated fee values.
|
|
|
|
|
+pub fn accumulate_fee(total: u64, fee: u64) -> FeeResult<u64> {
|
|
|
|
|
+ total.checked_add(fee).ok_or(FeeError::ArithmeticOverflow)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#[cfg(test)]
|
|
|
|
|
+mod tests {
|
|
|
|
|
+ use super::*;
|
|
|
|
|
+
|
|
|
|
|
+ #[test]
|
|
|
|
|
+ fn minimum_fee_uses_fixed_fee_per_gas() {
|
|
|
|
|
+ assert_eq!(minimum_fee(0).unwrap(), 0);
|
|
|
|
|
+ assert_eq!(minimum_fee(1).unwrap(), 5);
|
|
|
|
|
+ assert_eq!(minimum_fee(42).unwrap(), 210);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #[test]
|
|
|
|
|
+ fn fee_constant_validation_rejects_invalid_constants() {
|
|
|
|
|
+ assert_eq!(minimum_fee_with_constants(1, 0), Err(FeeError::InvalidFeeConstants));
|
|
|
|
|
+ assert_eq!(burn_fee_with_constants(1, 0, BURN_DEN), Err(FeeError::InvalidFeeConstants));
|
|
|
|
|
+ assert_eq!(burn_fee_with_constants(1, BURN_NUM, 0), Err(FeeError::InvalidFeeConstants));
|
|
|
|
|
+ assert_eq!(
|
|
|
|
|
+ burn_fee_with_constants(1, BURN_DEN, BURN_DEN),
|
|
|
|
|
+ Err(FeeError::InvalidFeeConstants)
|
|
|
|
|
+ );
|
|
|
|
|
+ assert_eq!(
|
|
|
|
|
+ burn_fee_with_constants(1, BURN_DEN + 1, BURN_DEN),
|
|
|
|
|
+ Err(FeeError::InvalidFeeConstants)
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #[test]
|
|
|
|
|
+ fn minimum_fee_rejects_multiplication_overflow() {
|
|
|
|
|
+ assert_eq!(minimum_fee(u64::MAX), Err(FeeError::ArithmeticOverflow));
|
|
|
|
|
+ assert_eq!(minimum_fee(u64::MAX / FEE_PER_GAS + 1), Err(FeeError::ArithmeticOverflow));
|
|
|
|
|
+ assert_eq!(
|
|
|
|
|
+ minimum_fee(u64::MAX / FEE_PER_GAS).unwrap(),
|
|
|
|
|
+ u64::MAX / FEE_PER_GAS * FEE_PER_GAS
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #[test]
|
|
|
|
|
+ fn burn_fee_rounds_down() {
|
|
|
|
|
+ assert_eq!(burn_fee(0).unwrap(), 0);
|
|
|
|
|
+ assert_eq!(burn_fee(1).unwrap(), 0);
|
|
|
|
|
+ assert_eq!(burn_fee(2).unwrap(), 1);
|
|
|
|
|
+ assert_eq!(burn_fee(3).unwrap(), 2);
|
|
|
|
|
+ assert_eq!(burn_fee(4).unwrap(), 3);
|
|
|
|
|
+ assert_eq!(burn_fee(5).unwrap(), 3);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #[test]
|
|
|
|
|
+ fn miner_claimable_includes_overpayment_tip() {
|
|
|
|
|
+ let minimum = minimum_fee(10).unwrap();
|
|
|
|
|
+ let burned = burn_fee(minimum).unwrap();
|
|
|
|
|
+
|
|
|
|
|
+ assert_eq!(minimum, 50);
|
|
|
|
|
+ assert_eq!(burned, 37);
|
|
|
|
|
+ assert_eq!(miner_claimable_fee(minimum, burned).unwrap(), 13);
|
|
|
|
|
+ assert_eq!(miner_claimable_fee(minimum + 7, burned).unwrap(), 20);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #[test]
|
|
|
|
|
+ fn miner_claimable_rejects_underflow() {
|
|
|
|
|
+ assert_eq!(miner_claimable_fee(2, 3), Err(FeeError::InsufficientFee));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #[test]
|
|
|
|
|
+ fn accumulated_fee_rejects_overflow() {
|
|
|
|
|
+ assert_eq!(accumulate_fee(7, 11).unwrap(), 18);
|
|
|
|
|
+ assert_eq!(accumulate_fee(u64::MAX, 1), Err(FeeError::ArithmeticOverflow));
|
|
|
|
|
+ }
|
|
|
|
|
+}
|