use std::cmp::Reverse;
use std::collections::HashMap;
use std::sync::Arc;
use miden_protocol::Word;
use miden_protocol::account::Account;
use miden_protocol::asset::{Asset, AssetAmount};
use miden_protocol::note::{Note, NoteId, Nullifier};
use miden_standards::note::AccountTargetNetworkNote;
use crate::chain_state::ChainState;
#[derive(Clone, Debug)]
pub struct SponsoredFeatureNote {
pub feature: AccountTargetNetworkNote,
pub sponsorships: Vec<Note>,
}
impl SponsoredFeatureNote {
pub fn num_notes(&self) -> usize {
1 + self.sponsorships.len()
}
pub fn retain_sponsorships_for_fee_asset(&mut self, fee_asset_id: Word) {
self.sponsorships.retain(|note| {
note.assets()
.as_slice()
.first()
.is_some_and(|asset| asset.id().to_word() == fee_asset_id)
});
}
pub fn sort_sponsorships_by_amount(&mut self) {
self.sponsorships.sort_by_key(|note| Reverse(sponsorship_amount(note)));
}
}
fn sponsorship_amount(note: &Note) -> AssetAmount {
note.assets()
.as_slice()
.first()
.and_then(Asset::as_fungible)
.map_or(AssetAmount::ZERO, |asset| asset.amount())
}
#[derive(Clone, Debug)]
pub struct TransactionCandidate {
pub account: Arc<Account>,
pub notes: Vec<SponsoredFeatureNote>,
pub chain_state: ChainState,
}
impl TransactionCandidate {
pub fn num_notes(&self) -> usize {
self.notes.iter().map(SponsoredFeatureNote::num_notes).sum()
}
pub fn sponsor_to_feature_nullifier(&self) -> HashMap<NoteId, Nullifier> {
self.notes
.iter()
.flat_map(|sponsored| {
let feature_nullifier = sponsored.feature.as_note().nullifier();
sponsored
.sponsorships
.iter()
.map(move |sponsorship| (sponsorship.id(), feature_nullifier))
})
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{
mock_network_account_id,
mock_single_target_note,
mock_sponsorship_note,
mock_sponsorship_note_with_amount,
};
fn sponsored(feature_seed: u8, num_sponsorships: u8) -> SponsoredFeatureNote {
let account_id = mock_network_account_id();
let feature = mock_single_target_note(account_id, feature_seed);
let sponsorships = (0..num_sponsorships)
.map(|i| {
mock_sponsorship_note(account_id, feature.as_note().id(), feature_seed + 100 + i)
})
.collect();
SponsoredFeatureNote { feature, sponsorships }
}
#[test]
fn sponsor_to_feature_nullifier_covers_sponsorships_only() {
let sponsored_notes = [sponsored(1, 2), sponsored(2, 0)];
let chain_state = ChainState::new(
crate::test_utils::mock_block_header(0_u32.into()),
miden_protocol::crypto::merkle::mmr::PartialMmr::default(),
miden_protocol::protocol_config::ProtocolConfig::mock(),
);
let candidate = TransactionCandidate {
account: Arc::new(crate::test_utils::mock_account(mock_network_account_id())),
notes: sponsored_notes.to_vec(),
chain_state,
};
let map = candidate.sponsor_to_feature_nullifier();
assert_eq!(map.len(), 2);
let feature_nullifier = sponsored_notes[0].feature.as_note().nullifier();
for sponsorship in &sponsored_notes[0].sponsorships {
assert_eq!(map[&sponsorship.id()], feature_nullifier);
}
assert_eq!(candidate.num_notes(), 4);
assert_eq!(
candidate.chain_state.protocol_config.as_ref(),
&miden_protocol::protocol_config::ProtocolConfig::mock()
);
}
#[test]
fn sort_sponsorships_by_amount_orders_largest_first() {
let account_id = mock_network_account_id();
let feature = mock_single_target_note(account_id, 1);
let feature_id = feature.as_note().id();
let sponsorships = vec![
mock_sponsorship_note_with_amount(account_id, feature_id, 2, 5),
mock_sponsorship_note_with_amount(account_id, feature_id, 3, 500),
mock_sponsorship_note_with_amount(account_id, feature_id, 4, 50),
];
let mut sponsored = SponsoredFeatureNote { feature, sponsorships };
sponsored.sort_sponsorships_by_amount();
let amounts = sponsored
.sponsorships
.iter()
.map(|note| sponsorship_amount(note).as_u64())
.collect::<Vec<_>>();
assert_eq!(amounts, [500, 50, 5]);
}
}