| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304 |
- //! State transition rules
- use cashu::{MeltQuoteState, State};
- /// State transition Error
- #[derive(thiserror::Error, Debug)]
- pub enum Error {
- /// Pending Token
- #[error("Token already pending for another update")]
- Pending,
- /// Already spent
- #[error("Token already spent")]
- AlreadySpent,
- /// Invalid transition
- #[error("Invalid transition: From {0} to {1}")]
- InvalidTransition(State, State),
- /// Already paid
- #[error("Quote already paid")]
- AlreadyPaid,
- /// Invalid transition
- #[error("Invalid melt quote state transition: From {0} to {1}")]
- InvalidMeltQuoteTransition(MeltQuoteState, MeltQuoteState),
- }
- #[inline]
- /// Check if the state transition is allowed
- pub fn check_state_transition(current_state: State, new_state: State) -> Result<(), Error> {
- let is_valid_transition = match current_state {
- State::Unspent => matches!(new_state, State::Pending | State::Spent),
- State::Pending => matches!(new_state, State::Unspent | State::Spent),
- // Any other state shouldn't be updated by the mint, and the wallet does not use this
- // function
- _ => false,
- };
- if !is_valid_transition {
- Err(match current_state {
- State::Pending => Error::Pending,
- State::Spent => Error::AlreadySpent,
- _ => Error::InvalidTransition(current_state, new_state),
- })
- } else {
- Ok(())
- }
- }
- #[inline]
- /// Check if the melt quote state transition is allowed
- ///
- /// Valid transitions:
- /// - Unpaid -> Pending, Failed
- /// - Pending -> Unpaid, Paid, Failed
- /// - Paid -> (no transitions allowed)
- /// - Failed -> Pending
- pub fn check_melt_quote_state_transition(
- current_state: MeltQuoteState,
- new_state: MeltQuoteState,
- ) -> Result<(), Error> {
- let is_valid_transition = match current_state {
- MeltQuoteState::Unpaid => {
- matches!(new_state, MeltQuoteState::Pending | MeltQuoteState::Failed)
- }
- MeltQuoteState::Pending => matches!(
- new_state,
- MeltQuoteState::Unpaid | MeltQuoteState::Paid | MeltQuoteState::Failed
- ),
- MeltQuoteState::Failed => {
- matches!(new_state, MeltQuoteState::Pending | MeltQuoteState::Unpaid)
- }
- MeltQuoteState::Paid => false,
- MeltQuoteState::Unknown => true,
- };
- if !is_valid_transition {
- Err(match current_state {
- MeltQuoteState::Pending => Error::Pending,
- MeltQuoteState::Paid => Error::AlreadyPaid,
- _ => Error::InvalidMeltQuoteTransition(current_state, new_state),
- })
- } else {
- Ok(())
- }
- }
- #[cfg(test)]
- mod tests {
- use super::*;
- mod proof_state_transitions {
- use super::*;
- #[test]
- fn unspent_to_pending_is_valid() {
- assert!(check_state_transition(State::Unspent, State::Pending).is_ok());
- }
- #[test]
- fn unspent_to_spent_is_valid() {
- assert!(check_state_transition(State::Unspent, State::Spent).is_ok());
- }
- #[test]
- fn pending_to_unspent_is_valid() {
- assert!(check_state_transition(State::Pending, State::Unspent).is_ok());
- }
- #[test]
- fn pending_to_spent_is_valid() {
- assert!(check_state_transition(State::Pending, State::Spent).is_ok());
- }
- #[test]
- fn unspent_to_unspent_is_invalid() {
- let result = check_state_transition(State::Unspent, State::Unspent);
- assert!(matches!(result, Err(Error::InvalidTransition(_, _))));
- }
- #[test]
- fn pending_to_pending_returns_pending_error() {
- let result = check_state_transition(State::Pending, State::Pending);
- assert!(matches!(result, Err(Error::Pending)));
- }
- #[test]
- fn spent_to_any_returns_already_spent() {
- assert!(matches!(
- check_state_transition(State::Spent, State::Unspent),
- Err(Error::AlreadySpent)
- ));
- assert!(matches!(
- check_state_transition(State::Spent, State::Pending),
- Err(Error::AlreadySpent)
- ));
- assert!(matches!(
- check_state_transition(State::Spent, State::Spent),
- Err(Error::AlreadySpent)
- ));
- }
- #[test]
- fn reserved_state_is_invalid_source() {
- let result = check_state_transition(State::Reserved, State::Unspent);
- assert!(matches!(result, Err(Error::InvalidTransition(_, _))));
- }
- }
- mod melt_quote_state_transitions {
- use super::*;
- #[test]
- fn unpaid_to_pending_is_valid() {
- assert!(check_melt_quote_state_transition(
- MeltQuoteState::Unpaid,
- MeltQuoteState::Pending
- )
- .is_ok());
- }
- #[test]
- fn unpaid_to_failed_is_valid() {
- assert!(check_melt_quote_state_transition(
- MeltQuoteState::Unpaid,
- MeltQuoteState::Failed
- )
- .is_ok());
- }
- #[test]
- fn pending_to_unpaid_is_valid() {
- assert!(check_melt_quote_state_transition(
- MeltQuoteState::Pending,
- MeltQuoteState::Unpaid
- )
- .is_ok());
- }
- #[test]
- fn pending_to_paid_is_valid() {
- assert!(check_melt_quote_state_transition(
- MeltQuoteState::Pending,
- MeltQuoteState::Paid
- )
- .is_ok());
- }
- #[test]
- fn pending_to_failed_is_valid() {
- assert!(check_melt_quote_state_transition(
- MeltQuoteState::Pending,
- MeltQuoteState::Failed
- )
- .is_ok());
- }
- #[test]
- fn failed_to_pending_is_valid() {
- assert!(check_melt_quote_state_transition(
- MeltQuoteState::Failed,
- MeltQuoteState::Pending
- )
- .is_ok());
- }
- #[test]
- fn failed_to_unpaid_is_valid() {
- assert!(check_melt_quote_state_transition(
- MeltQuoteState::Failed,
- MeltQuoteState::Unpaid
- )
- .is_ok());
- }
- #[test]
- fn unknown_to_any_is_valid() {
- assert!(check_melt_quote_state_transition(
- MeltQuoteState::Unknown,
- MeltQuoteState::Unpaid
- )
- .is_ok());
- assert!(check_melt_quote_state_transition(
- MeltQuoteState::Unknown,
- MeltQuoteState::Pending
- )
- .is_ok());
- assert!(check_melt_quote_state_transition(
- MeltQuoteState::Unknown,
- MeltQuoteState::Paid
- )
- .is_ok());
- assert!(check_melt_quote_state_transition(
- MeltQuoteState::Unknown,
- MeltQuoteState::Failed
- )
- .is_ok());
- }
- #[test]
- fn unpaid_to_paid_is_invalid() {
- let result =
- check_melt_quote_state_transition(MeltQuoteState::Unpaid, MeltQuoteState::Paid);
- assert!(matches!(
- result,
- Err(Error::InvalidMeltQuoteTransition(_, _))
- ));
- }
- #[test]
- fn unpaid_to_unpaid_is_invalid() {
- let result =
- check_melt_quote_state_transition(MeltQuoteState::Unpaid, MeltQuoteState::Unpaid);
- assert!(matches!(
- result,
- Err(Error::InvalidMeltQuoteTransition(_, _))
- ));
- }
- #[test]
- fn pending_to_pending_returns_pending_error() {
- let result =
- check_melt_quote_state_transition(MeltQuoteState::Pending, MeltQuoteState::Pending);
- assert!(matches!(result, Err(Error::Pending)));
- }
- #[test]
- fn paid_to_any_returns_already_paid() {
- assert!(matches!(
- check_melt_quote_state_transition(MeltQuoteState::Paid, MeltQuoteState::Unpaid),
- Err(Error::AlreadyPaid)
- ));
- assert!(matches!(
- check_melt_quote_state_transition(MeltQuoteState::Paid, MeltQuoteState::Pending),
- Err(Error::AlreadyPaid)
- ));
- assert!(matches!(
- check_melt_quote_state_transition(MeltQuoteState::Paid, MeltQuoteState::Paid),
- Err(Error::AlreadyPaid)
- ));
- assert!(matches!(
- check_melt_quote_state_transition(MeltQuoteState::Paid, MeltQuoteState::Failed),
- Err(Error::AlreadyPaid)
- ));
- }
- #[test]
- fn failed_to_paid_is_invalid() {
- let result =
- check_melt_quote_state_transition(MeltQuoteState::Failed, MeltQuoteState::Paid);
- assert!(matches!(
- result,
- Err(Error::InvalidMeltQuoteTransition(_, _))
- ));
- }
- #[test]
- fn failed_to_failed_is_invalid() {
- let result =
- check_melt_quote_state_transition(MeltQuoteState::Failed, MeltQuoteState::Failed);
- assert!(matches!(
- result,
- Err(Error::InvalidMeltQuoteTransition(_, _))
- ));
- }
- }
- }
|