use alloc::vec::Vec;
use miden_protocol::account::AccountId;
use miden_protocol::errors::NoteError;
use miden_protocol::{Felt, Hasher, Word};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FeeConversionInfo {
faucet_id: AccountId,
rate_num: Felt,
rate_den: Felt,
}
impl FeeConversionInfo {
pub fn new(faucet_id: AccountId, rate_num: u64, rate_den: u64) -> Result<Self, NoteError> {
if rate_num == 0 {
return Err(NoteError::other("fee conversion rate numerator must be non-zero"));
}
if rate_den == 0 {
return Err(NoteError::other("fee conversion rate denominator must be non-zero"));
}
let rate_num = Felt::try_from(rate_num).map_err(|err| {
NoteError::other_with_source("fee conversion rate numerator is not a valid felt", err)
})?;
let rate_den = Felt::try_from(rate_den).map_err(|err| {
NoteError::other_with_source("fee conversion rate denominator is not a valid felt", err)
})?;
Ok(Self { faucet_id, rate_num, rate_den })
}
pub fn one_to_one(faucet_id: AccountId) -> Self {
Self {
faucet_id,
rate_num: Felt::ONE,
rate_den: Felt::ONE,
}
}
pub fn faucet_id(&self) -> AccountId {
self.faucet_id
}
pub fn rate_num(&self) -> Felt {
self.rate_num
}
pub fn rate_den(&self) -> Felt {
self.rate_den
}
pub fn to_word(&self) -> Word {
Word::from([
self.faucet_id.suffix(),
self.faucet_id.prefix().as_felt(),
self.rate_num,
self.rate_den,
])
}
}
pub fn commit_fee_conversion_info(
conversion_info: FeeConversionInfo,
salt: Word,
) -> (Word, Vec<Felt>) {
let info_word = conversion_info.to_word();
let mut value = Vec::with_capacity(8);
value.extend(salt.iter());
value.extend(info_word.iter());
(Hasher::merge(&[info_word, salt]), value)
}
#[cfg(test)]
mod tests {
use miden_protocol::account::AccountType;
use super::*;
fn faucet() -> AccountId {
AccountId::builder()
.account_type(AccountType::Public)
.build_with_seed([3u8; 32])
}
#[test]
fn zero_rates_are_rejected() {
assert!(FeeConversionInfo::new(faucet(), 0, 1).is_err());
assert!(FeeConversionInfo::new(faucet(), 1, 0).is_err());
assert!(FeeConversionInfo::new(faucet(), 1, 1).is_ok());
}
#[test]
fn rates_exceeding_field_modulus_are_rejected() {
assert!(FeeConversionInfo::new(faucet(), u64::MAX, 1).is_err());
assert!(FeeConversionInfo::new(faucet(), 1, u64::MAX).is_err());
assert!(FeeConversionInfo::new(faucet(), 10u64.pow(16), 10u64.pow(4)).is_ok());
}
#[test]
fn advice_map_value_is_commitment_preimage() {
let payment_info = FeeConversionInfo::new(faucet(), 2, 3).unwrap();
let salt = Word::from([1u32, 2, 3, 4]);
let (key, value) = commit_fee_conversion_info(payment_info, salt);
assert_eq!(key, Hasher::merge(&[payment_info.to_word(), salt]));
assert_eq!(value.len(), 8);
assert_eq!(&value[..4], salt.as_elements());
assert_eq!(&value[4..], payment_info.to_word().as_elements());
}
}