audit-3 (code cleanup): zero clippy warnings, zero build warnings

prep for deployment. cargo clippy --workspace --all-targets now passes
clean. cargo audit unchanged (same 2 unmaintained-warning macro-support
transitives; no cves).

cleanup applied:
- ProtocolParams construction in tools.rs uses struct-update syntax
  (clippy::field_reassign_with_default).
- main.rs collapsed two else-if branches with identical bodies
  (clippy::if_same_then_else).
- mint/plutus/stake sort_by(|a,b| b.cmp(&a)) → sort_by_key(Reverse(_))
  (clippy::manual_sort_by). 4 sites.
- metadata/mint/tx odd-length hex check uses .is_multiple_of(2)
  (clippy::manual_is_multiple_of). 3 sites.
- stake.rs witness_overhead conditional removed — both branches
  produced TWO_WITNESS_OVERHEAD_BYTES (left over from when
  registration was thought to add a third witness; it doesn't).
  WITNESS_OVERHEAD_BYTES const removed (only the two-witness one
  is used).
- Public spend/mint/stake build_signed_*_with_assets fns get
  #[allow(clippy::too_many_arguments)] — they ARE the API surface.
- ex_units_default_is_generous test gets explicit allow for the
  tautological-on-const assertion (kept the intent comment).

97 unit tests still pass. release build clean.
This commit is contained in:
Sulkta 2026-05-04 18:40:35 -07:00
parent 3bf2be28a7
commit 6ed378a486
7 changed files with 30 additions and 25 deletions

View file

@ -171,7 +171,7 @@ pub fn build_cip25_aux_data(
} }
fn decode_hex(s: &str) -> Result<Vec<u8>, WalletError> { fn decode_hex(s: &str) -> Result<Vec<u8>, WalletError> {
if s.len() % 2 != 0 { if !s.len().is_multiple_of(2) {
return Err(WalletError::Derivation("hex string odd length".into())); return Err(WalletError::Derivation("hex string odd length".into()));
} }
let mut out = Vec::with_capacity(s.len() / 2); let mut out = Vec::with_capacity(s.len() / 2);

View file

@ -181,7 +181,7 @@ fn parse_tx_hash(hex_str: &str) -> Result<Hash<32>, WalletError> {
} }
fn parse_asset_name(hex_str: &str) -> Result<Vec<u8>, WalletError> { fn parse_asset_name(hex_str: &str) -> Result<Vec<u8>, WalletError> {
if hex_str.len() % 2 != 0 { if !hex_str.len().is_multiple_of(2) {
return Err(WalletError::Derivation( return Err(WalletError::Derivation(
"asset_name hex must have even length".into(), "asset_name hex must have even length".into(),
)); ));
@ -259,7 +259,7 @@ fn prepare_mint(
.ok_or_else(|| WalletError::Derivation("amount overflow".into()))?; .ok_or_else(|| WalletError::Derivation("amount overflow".into()))?;
let mut sorted: Vec<InputUtxo> = available_utxos.to_vec(); let mut sorted: Vec<InputUtxo> = available_utxos.to_vec();
sorted.sort_by(|a, b| b.lovelace.cmp(&a.lovelace)); sorted.sort_by_key(|u| std::cmp::Reverse(u.lovelace));
let mut acc: u64 = 0; let mut acc: u64 = 0;
let mut selected: Vec<InputUtxo> = Vec::new(); let mut selected: Vec<InputUtxo> = Vec::new();
for u in sorted { for u in sorted {
@ -703,7 +703,7 @@ pub fn build_signed_cip68_nft_mint(
.ok_or_else(|| WalletError::Derivation("amount overflow".into()))?; .ok_or_else(|| WalletError::Derivation("amount overflow".into()))?;
let mut sorted: Vec<InputUtxo> = available_utxos.to_vec(); let mut sorted: Vec<InputUtxo> = available_utxos.to_vec();
sorted.sort_by(|a, b| b.lovelace.cmp(&a.lovelace)); sorted.sort_by_key(|u| std::cmp::Reverse(u.lovelace));
let mut acc: u64 = 0; let mut acc: u64 = 0;
let mut selected: Vec<InputUtxo> = Vec::new(); let mut selected: Vec<InputUtxo> = Vec::new();
for u in sorted { for u in sorted {

View file

@ -145,7 +145,7 @@ pub fn build_signed_plutus_spend(
.filter(|u| u.assets.is_empty()) .filter(|u| u.assets.is_empty())
.cloned() .cloned()
.collect(); .collect();
ada_only.sort_by(|a, b| b.lovelace.cmp(&a.lovelace)); ada_only.sort_by_key(|u| std::cmp::Reverse(u.lovelace));
// Pick collateral — largest ADA-only UTXO ≥ 5 ADA. // Pick collateral — largest ADA-only UTXO ≥ 5 ADA.
let collateral = ada_only let collateral = ada_only
@ -359,7 +359,11 @@ mod tests {
const UNIT_REDEEMER_CBOR: [u8; 3] = [0xd8, 0x79, 0x80]; const UNIT_REDEEMER_CBOR: [u8; 3] = [0xd8, 0x79, 0x80];
#[test] #[test]
#[allow(clippy::assertions_on_constants)]
fn ex_units_default_is_generous() { fn ex_units_default_is_generous() {
// Tautological today (DEFAULT_EX_UNITS is a const), but the
// intent is to guard against a future shrink that would make
// trivial validators fail to validate. Keep the check.
assert!(DEFAULT_EX_UNITS.mem >= 1_000_000); assert!(DEFAULT_EX_UNITS.mem >= 1_000_000);
assert!(DEFAULT_EX_UNITS.steps >= 100_000_000); assert!(DEFAULT_EX_UNITS.steps >= 100_000_000);
} }

View file

@ -24,9 +24,9 @@ use crate::{Network, PaymentKey, ProtocolParams, StakeKey, WalletError};
/// Shelley). Refunded on deregistration. /// Shelley). Refunded on deregistration.
pub const STAKE_KEY_DEPOSIT_LOVELACE: u64 = 2_000_000; pub const STAKE_KEY_DEPOSIT_LOVELACE: u64 = 2_000_000;
const WITNESS_OVERHEAD_BYTES: u64 = 128;
/// Two witnesses (payment + stake) instead of just one — used for fee /// Two witnesses (payment + stake) instead of just one — used for fee
/// estimation when both keys sign. /// estimation. Both `register_first` paths sign with both keys, so the
/// witness overhead is constant.
const TWO_WITNESS_OVERHEAD_BYTES: u64 = 256; const TWO_WITNESS_OVERHEAD_BYTES: u64 = 256;
/// Decode a `pool1...` bech32 pool ID into a 28-byte Hash. /// Decode a `pool1...` bech32 pool ID into a 28-byte Hash.
@ -84,6 +84,7 @@ fn network_id_for(network: Network) -> u8 {
/// ///
/// The tx is signed by both the payment key (body witness) and the /// The tx is signed by both the payment key (body witness) and the
/// stake key (cert witness). Returned CBOR is ready for submission. /// stake key (cert witness). Returned CBOR is ready for submission.
#[allow(clippy::too_many_arguments)]
pub fn build_signed_stake_delegation( pub fn build_signed_stake_delegation(
payment_key: &PaymentKey, payment_key: &PaymentKey,
stake_key: &StakeKey, stake_key: &StakeKey,
@ -129,7 +130,7 @@ pub fn build_signed_stake_delegation(
.ok_or_else(|| WalletError::Derivation("amount overflow".into()))?; .ok_or_else(|| WalletError::Derivation("amount overflow".into()))?;
let mut sorted: Vec<InputUtxo> = available_utxos.to_vec(); let mut sorted: Vec<InputUtxo> = available_utxos.to_vec();
sorted.sort_by(|a, b| b.lovelace.cmp(&a.lovelace)); sorted.sort_by_key(|u| std::cmp::Reverse(u.lovelace));
let mut acc: u64 = 0; let mut acc: u64 = 0;
let mut selected: Vec<InputUtxo> = Vec::new(); let mut selected: Vec<InputUtxo> = Vec::new();
for u in sorted { for u in sorted {
@ -210,15 +211,9 @@ pub fn build_signed_stake_delegation(
.map_err(|e| WalletError::Derivation(format!("conway build (pass1): {e}")))? .map_err(|e| WalletError::Derivation(format!("conway build (pass1): {e}")))?
.tx_bytes .tx_bytes
.0; .0;
// Two witnesses for delegation: payment + stake. Fee estimate // Both delegation cases sign with two witnesses (payment + stake);
// accounts for both. // registration doesn't add a third.
let witness_overhead = if register_first { let est_signed = (unsigned.len() as u64) + TWO_WITNESS_OVERHEAD_BYTES;
TWO_WITNESS_OVERHEAD_BYTES
} else {
TWO_WITNESS_OVERHEAD_BYTES
};
let _ = WITNESS_OVERHEAD_BYTES; // silence unused if both branches use the two-witness constant
let est_signed = (unsigned.len() as u64) + witness_overhead;
let real_fee = params.min_fee_for_size(est_signed); let real_fee = params.min_fee_for_size(est_signed);
let token_change = !input_assets.is_empty(); let token_change = !input_assets.is_empty();

View file

@ -191,7 +191,7 @@ fn parse_policy_id(hex_str: &str) -> Result<pallas_crypto::hash::Hash<28>, Walle
} }
fn parse_asset_name(hex_str: &str) -> Result<Vec<u8>, WalletError> { fn parse_asset_name(hex_str: &str) -> Result<Vec<u8>, WalletError> {
if hex_str.len() % 2 != 0 { if !hex_str.len().is_multiple_of(2) {
return Err(WalletError::Derivation( return Err(WalletError::Derivation(
"asset_name hex must have even length".into(), "asset_name hex must have even length".into(),
)); ));
@ -364,6 +364,7 @@ fn output_with_assets(
Ok(out) Ok(out)
} }
#[allow(clippy::too_many_arguments)]
fn build_staging_with_fee( fn build_staging_with_fee(
inputs: &[InputUtxo], inputs: &[InputUtxo],
to_addr: &PallasAddress, to_addr: &PallasAddress,
@ -632,7 +633,7 @@ fn hex_encode(bytes: &[u8]) -> String {
/// Hex-decode a string into a Vec — used by the cold-sign flow to /// Hex-decode a string into a Vec — used by the cold-sign flow to
/// turn the externally-signed tx hex back into bytes for submission. /// turn the externally-signed tx hex back into bytes for submission.
pub fn hex_decode(s: &str) -> Result<Vec<u8>, WalletError> { pub fn hex_decode(s: &str) -> Result<Vec<u8>, WalletError> {
if s.len() % 2 != 0 { if !s.len().is_multiple_of(2) {
return Err(WalletError::Derivation("hex string has odd length".into())); return Err(WalletError::Derivation("hex string has odd length".into()));
} }
let mut out = Vec::with_capacity(s.len() / 2); let mut out = Vec::with_capacity(s.len() / 2);
@ -671,6 +672,7 @@ pub fn build_signed_payment(
/// Build + sign a Conway-era payment that may include native assets. /// Build + sign a Conway-era payment that may include native assets.
/// Returns the signed CBOR bytes ready for `ChainBackend::submit_tx`. /// Returns the signed CBOR bytes ready for `ChainBackend::submit_tx`.
#[allow(clippy::too_many_arguments)]
pub fn build_signed_payment_with_assets( pub fn build_signed_payment_with_assets(
payment_key: &PaymentKey, payment_key: &PaymentKey,
network: Network, network: Network,
@ -722,6 +724,7 @@ pub fn build_unsigned_payment(
/// signing. Returns the unsigned CBOR + a `PaymentSummary` for human /// signing. Returns the unsigned CBOR + a `PaymentSummary` for human
/// review. Caller signs externally and submits via /// review. Caller signs externally and submits via
/// `ChainBackend::submit_tx`. /// `ChainBackend::submit_tx`.
#[allow(clippy::too_many_arguments)]
pub fn build_unsigned_payment_with_assets( pub fn build_unsigned_payment_with_assets(
network: Network, network: Network,
available_utxos: &[InputUtxo], available_utxos: &[InputUtxo],
@ -887,7 +890,7 @@ mod tests {
#[test] #[test]
fn parse_asset_name_validates_length() { fn parse_asset_name_validates_length() {
assert!(parse_asset_name(&"ab".repeat(33)).is_err()); // > 32 bytes assert!(parse_asset_name(&"ab".repeat(33)).is_err()); // > 32 bytes
assert!(parse_asset_name(&"deadbeef").is_ok()); // 4 bytes assert!(parse_asset_name("deadbeef").is_ok()); // 4 bytes
assert!(parse_asset_name("").is_ok()); // 0 bytes — valid (ADA-symbol-like) assert!(parse_asset_name("").is_ok()); // 0 bytes — valid (ADA-symbol-like)
} }

View file

@ -82,9 +82,10 @@ async fn run() -> Result<()> {
let (payment_key, stake_key, address) = { let (payment_key, stake_key, address) = {
let root = if bootstrap_new { let root = if bootstrap_new {
bootstrap::generate_and_save_root_key(&cfg.data_dir)? bootstrap::generate_and_save_root_key(&cfg.data_dir)?
} else if mnemonic_path.exists() { } else if mnemonic_path.exists() || bootstrap_only {
bootstrap::load_or_create_root_key(&cfg.data_dir)? // Both branches load (or create on the bootstrap path).
} else if bootstrap_only { // `load_or_create_root_key` itself decides which based on
// whether `mnemonic.age` exists.
bootstrap::load_or_create_root_key(&cfg.data_dir)? bootstrap::load_or_create_root_key(&cfg.data_dir)?
} else { } else {
anyhow::bail!( anyhow::bail!(

View file

@ -826,8 +826,10 @@ impl WalletService {
// verify script_data_hash. Hardcoded preprod value from // verify script_data_hash. Hardcoded preprod value from
// koios epoch_params; future improvement is pulling fresh // koios epoch_params; future improvement is pulling fresh
// from the chain on each call. (PLUTUS-4 audit fix.) // from the chain on each call. (PLUTUS-4 audit fix.)
let mut params = ProtocolParams::default(); let params = ProtocolParams {
params.plutus_v3_cost_model = Some(PLUTUS_V3_COST_MODEL_PREPROD.to_vec()); plutus_v3_cost_model: Some(PLUTUS_V3_COST_MODEL_PREPROD.to_vec()),
..ProtocolParams::default()
};
let cbor = build_signed_plutus_spend( let cbor = build_signed_plutus_spend(
&self.inner.payment_key, &self.inner.payment_key,