iuna

iuna

iuna - experimental mainnet-candidate protocol
git clone https://getiuna.org/git/iuna.git
Log | Files | Refs | README | LICENSE

commit 99aac16025652612dc304d505c41aa4ae20c2068
parent da0d834384b507da38af0c37e677e45db2a6734b
Author: Joris Hartog <jorishartog@hotmail.com>
Date:   Mon, 17 Aug 2026 18:51:42 +0200

Fix automatic burn fees after activation

Diffstat:
Msrc/app/helpers.rs | 60+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/domain/tests.rs | 23+++++++++++++++++++++++
2 files changed, 82 insertions(+), 1 deletion(-)

diff --git a/src/app/helpers.rs b/src/app/helpers.rs @@ -18,7 +18,7 @@ pub(super) fn converge_fee_by_byte( fee_per_byte: Amount, mut build: impl FnMut(Amount) -> Result<BuiltBlindedTransaction>, ) -> Result<(BuiltBlindedTransaction, FeeEstimate)> { - let mut fee = 0; + let mut fee = if fee_per_byte == 0 { 0 } else { 1 }; let mut best = None; for _ in 0..64 { let built = build(fee)?; @@ -129,3 +129,61 @@ pub(super) fn recovery_vdf_sample_percent(address: &str, tip_hash: &str) -> u8 { let digest = Sha256::digest(format!("iuna-recovery-vdf-sample:{tip_hash}:{address}")); digest[0] % 100 } + +#[cfg(test)] +mod tests { + use anyhow::bail; + + use crate::domain::{BlindedReveal, BlindedTransaction, BuiltBlindedTransaction, Transaction}; + + use super::converge_fee_by_byte; + + fn built_with_fee(fee: u64) -> BuiltBlindedTransaction { + BuiltBlindedTransaction { + payload: Transaction::Burn { + inputs: Vec::new(), + change: Vec::new(), + amount: 1, + fee, + signature: "payload".to_string(), + }, + transaction: BlindedTransaction { + commitment: "0".repeat(64), + inputs: Vec::new(), + fee, + encrypted_size: 1, + expires_at_height: 1, + nonce: "0".repeat(24), + ciphertext: "00".to_string(), + payload_hash: "1".repeat(64), + }, + reveal: BlindedReveal { + commitment: "0".repeat(64), + key: "2".repeat(64), + }, + } + } + + #[test] + fn fee_convergence_does_not_probe_zero_when_fee_rate_is_positive() { + let (built, estimate) = converge_fee_by_byte(1, |fee| { + if fee == 0 { + bail!("zero fee rejected after activation"); + } + Ok(built_with_fee(fee)) + }) + .expect("positive fee-rate convergence should skip invalid zero fee"); + + assert!(estimate.fee > 0); + assert_eq!(built.transaction.fee, estimate.fee); + } + + #[test] + fn fee_convergence_allows_zero_when_fee_rate_is_zero() { + let (built, estimate) = + converge_fee_by_byte(0, |fee| Ok(built_with_fee(fee))).expect("zero fee rate works"); + + assert_eq!(estimate.fee, 0); + assert_eq!(built.transaction.fee, 0); + } +} diff --git a/src/domain/tests.rs b/src/domain/tests.rs @@ -2290,6 +2290,29 @@ fn zero_fee_blinded_transaction_creation_is_rejected_from_height_750() { assert!(format!("{error:#}").contains("must pay a fee from height 750")); } +#[test] +fn automatic_burn_builds_paid_blinded_transaction_from_height_750() { + let alice = Wallet::from_seed("auto-burn-after-fee-activation-alice"); + let mut ledger = ledger_with_allocation(&alice, 10 * MICRO_IUNA); + set_tip_height_for_validation(&mut ledger, BLOCK_ITEM_FEES_REQUIRED_HEIGHT - 1); + let mut node = + crate::app::NodeCore::from_ledger_with_burn_fee_and_enabled(alice, ledger, true, 50, 1); + + let plan = node.prepare_automatic_mining(1); + + let burned = plan + .burned + .as_ref() + .expect("automatic burn should be built at fee activation"); + assert_eq!(burned.amount(), 50); + assert!(burned.fee() > 0); + assert_eq!(node.ledger().pending_blinded_transactions().len(), 1); + assert_eq!( + node.ledger().pending_blinded_transactions()[0].fee, + burned.fee() + ); +} + fn block_for_fee_policy(miner: &Wallet, height: u64, transactions: Vec<Transaction>) -> Block { Block { height,