diff --git a/src/c1_state_machine/mod.rs b/src/c1_state_machine/mod.rs index 8cbe899..e4c11bc 100644 --- a/src/c1_state_machine/mod.rs +++ b/src/c1_state_machine/mod.rs @@ -28,7 +28,7 @@ pub trait StateMachine { } /// A set of play users for experimenting with the multi-user state machines -#[derive(Hash, Eq, PartialEq, Debug, Clone)] +#[derive(Hash, Eq, PartialEq, Debug, Clone, Ord, PartialOrd)] pub enum User { Alice, Bob, diff --git a/src/c1_state_machine/p5_digital_cash.rs b/src/c1_state_machine/p5_digital_cash.rs index bdd0a66..aea0c72 100644 --- a/src/c1_state_machine/p5_digital_cash.rs +++ b/src/c1_state_machine/p5_digital_cash.rs @@ -4,7 +4,7 @@ //! When a state transition spends bills, new bills are created in lesser or equal amount. use super::{StateMachine, User}; -use std::collections::HashSet; +use std::collections::{HashSet, BTreeSet}; /// This state machine models a multi-user currency system. It tracks a set of bills in /// circulation, and updates that set when money is transferred. @@ -13,14 +13,22 @@ pub struct DigitalCashSystem; /// A single bill in the digital cash system. Each bill has an owner who is allowed to spent /// it and an amount that it is worth. It also has serial number to ensure that each bill /// is unique. +#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)] pub struct Bill { owner: User, amount: u64, serial: u64, } +impl Bill { + pub fn amount(&self) -> u64 { + self.amount + } +} + /// The State of a digital cash system. Primarily just the set of currently circulating bills., /// but also a counter for the next serial number. +#[derive(Clone, Debug, Eq, PartialEq)] pub struct State { /// The set of currently circulating bills bills: HashSet, @@ -28,6 +36,58 @@ pub struct State { next_serial: u64, } +impl State { + pub fn new() -> Self { + State { bills: HashSet::::new(), next_serial: 0 } + } + + pub fn set_serial(&mut self, serial: u64) { + self.next_serial = serial; + } + + pub fn insert_bill(&mut self, bill: Bill) -> bool { + self.bills.insert(bill) + } + + pub fn remove_bill(&mut self, bill: Bill) -> bool { + self.bills.remove(&bill) + } + + pub fn contains_bill(&self, bill: Bill) -> bool { + self.bills.contains(&bill) + } + + pub fn next_serial(&self) -> u64 { + self.next_serial + } + + fn increment_serial(&mut self) { + self.next_serial += 1 + } + + fn add_bill(&mut self, elem: Bill) { + self.bills.insert(elem); + self.increment_serial() + } +} + +impl FromIterator for State { + fn from_iter>(iter: I) -> Self { + let mut state = State::new(); + + for i in iter { + state.add_bill(i) + } + state + } +} + +impl From<[Bill; N]> for State { + fn from(value : [Bill; N]) -> Self { + State::from_iter(value) + } +} + /// The state transitions that users can make in a digital cash system pub enum CashTransaction { /// Mint a single new bill owned by the minter @@ -48,9 +108,430 @@ impl StateMachine for DigitalCashSystem { type State = State; type Transition = CashTransaction; - fn next_state(starting_state: &State, t: &CashTransaction) -> State { - todo!("Exercise 1") + fn next_state(starting_state: &Self::State, t: &Self::Transition) -> Self::State { + match t { + CashTransaction::Mint { minter, amount} => handle_mint(starting_state.clone(), minter.clone(), *amount), + CashTransaction::Transfer { spends, receives } => handle_transfer(starting_state.clone(), spends.clone(), receives.clone()) + } + } +} + +fn handle_mint(starting_state: State, minter: User, amount: u64) -> State { + let mut next_state = starting_state; + let bill = Bill { owner: minter.clone(), amount: amount, serial: next_state.next_serial() }; + next_state.insert_bill(bill); + next_state.increment_serial(); + next_state +} + +fn handle_transfer(starting_state: State, spends: Vec, receives: Vec) -> State { + if spends.is_empty() || receives.is_empty() { + return starting_state; + } + let mut next_state = starting_state.clone(); + + { + let input_set: BTreeSet<_> = spends.iter().collect(); + if input_set.len() < spends.len() { + return starting_state; + } + } + + { + let output_set: BTreeSet<_> = receives.iter().collect(); + if output_set.len() < receives.len() { + return starting_state; + } + } + + let mut total_input: u64 = 0; + let mut total_output: u64 = 0; + + for input in spends.iter() { + let amount = input.amount(); + let bill = input.clone(); + if !next_state.contains_bill(bill) { + return starting_state; + } + + if total_input.checked_add(amount).is_none() { + return starting_state; + } + total_input += amount; + } + + for output in receives.iter() { + let amount = output.amount; + let bill = output.clone(); + let serial = bill.serial; + + if amount <= 0 { + return starting_state; + } + + if next_state.next_serial().checked_add(1).is_none() { + return starting_state; + } + + if next_state.contains_bill(bill) { + return starting_state; + } + + if total_output.checked_add(amount).is_none() { + return starting_state; + } + total_output += amount; + } + + if total_output > total_input { + return starting_state; + } + + // update storage + + for input in spends { + next_state.remove_bill(input); + } + + for output in receives { + // add bill to the map and increment serial safely. + if next_state.next_serial().checked_add(1).is_none() { + return starting_state; + } + + if output.serial != next_state.next_serial { + return starting_state; + } + + let bill = Bill { owner: output.owner, amount: output.amount, serial: next_state.next_serial }; + next_state.increment_serial(); + next_state.insert_bill(bill); } + + next_state +} + +#[test] +fn mint_new_cash() { + let start = State::new(); + let end = DigitalCashSystem::next_state( + &start, + &CashTransaction::Mint { + minter: User::Alice, + amount: 20, + } + ); + + let expected = State::from([ + Bill { owner: User::Alice, amount: 20, serial: 0 } + ]); + assert_eq!(end, expected); +} + +#[test] +fn overflow_receives() { + let start = State::from([ + Bill { owner: User::Alice, amount: 42, serial: 0}, + ]); + let end = DigitalCashSystem::next_state( + &start, + &CashTransaction::Transfer { + spends: vec![Bill { owner: User::Alice, amount: 42, serial: 0 }], + receives: vec![ + Bill { owner: User::Alice, amount: u64::MAX, serial: 1 }, + Bill { owner: User::Alice, amount: 42, serial: 2 } + ] + } + ); + let expected = State::from([ + Bill { owner: User::Alice, amount: 42, serial: 0} + ]); + assert_eq!(end, expected); +} + +#[test] +fn empty_spend_fails() { + let start = State::from([ + Bill { owner: User::Alice, amount: 20, serial: 0 } + ]); + let end = DigitalCashSystem::next_state( + &start, + &CashTransaction::Transfer { + spends: vec![], + receives: vec![Bill { owner: User::Alice, amount: 15, serial: 1 }] + } + ); + let expected = State::from([ + Bill { owner: User::Alice, amount: 20, serial: 0 } + ]); + assert_eq!(end, expected); +} + +#[test] +fn empty_receive_fails() { + let start = State::from([ + Bill { owner: User::Alice, amount: 20, serial: 0 } + ]); + let end = DigitalCashSystem::next_state( + &start, + &CashTransaction::Transfer { + spends: vec![Bill { owner: User::Alice, amount: 20, serial: 0 }], + receives: vec![] + } + ); + let expected = State::from([ + Bill { owner: User::Alice, amount: 20, serial: 0 } + ]); + assert_eq!(end, expected); } -// TODO lots of tests +#[test] +fn output_value_0_fails() { + let start = State::from([ + Bill { owner: User::Alice, amount: 20, serial: 0 } + ]); + let end = DigitalCashSystem::next_state( + &start, + &CashTransaction::Transfer { + spends: vec![Bill { owner: User::Alice, amount: 20, serial: 0 }], + receives: vec![Bill { owner: User::Bob, amount: 0, serial: 1 }] + } + ); + let expected = State::from([ + Bill { owner: User::Alice, amount: 20, serial: 0 } + ]); + assert_eq!(end, expected); +} + +#[test] +fn serial_number_already_seen_fails() { + let start = State::from([ + Bill { owner: User::Alice, amount: 20, serial: 0 } + ]); + let end = DigitalCashSystem::next_state( + &start, + &CashTransaction::Transfer { + spends: vec![Bill { owner: User::Alice, amount: 20, serial: 0 }], + receives: vec![Bill { owner: User::Alice, amount: 18, serial: 0 }] + } + ); + let expected = State::from([ + Bill { owner: User::Alice, amount: 20, serial: 0 } + ]); + assert_eq!(end, expected); +} + +#[test] +fn spending_and_receiving_same_bill_fails() { + let start = State::from([ + Bill { owner: User::Alice, amount: 20, serial: 0 } + ]); + let end = DigitalCashSystem::next_state( + &start, + &CashTransaction::Transfer { + spends: vec![Bill { owner: User::Alice, amount: 20, serial: 0 }], + receives: vec![Bill { owner: User::Alice, amount: 20, serial: 0 }] + } + ); + let expected = State::from([ + Bill { owner: User::Alice, amount: 20, serial: 0 } + ]); + assert_eq!(end, expected); +} + +#[test] +fn receiving_bill_with_incorrect_serial_fails() { + let start = State::from([ + Bill { owner: User::Alice, amount: 20, serial: 0 } + ]); + let end = DigitalCashSystem::next_state( + &start, + &CashTransaction::Transfer { + spends: vec![Bill { owner: User::Alice, amount: 20, serial: 0 }], + receives: vec![ + Bill { owner: User::Alice, amount: 10, serial: u64::MAX }, + Bill { owner: User::Bob, amount: 10, serial: 4000 } + ] + } + ); + let expected = State::from([ + Bill { owner: User::Alice, amount: 20, serial: 0 } + ]); + assert_eq!(end, expected); +} + +#[test] +fn spending_bill_with_incorrect_amount_fails() { + let start = State::from([ + Bill { owner: User::Alice, amount: 20, serial: 0 } + ]); + let end = DigitalCashSystem::next_state( + &start, + &CashTransaction::Transfer { + spends: vec![Bill { owner: User::Alice, amount: 40, serial: 0 }], + receives: vec![Bill { owner: User::Bob, amount: 40, serial: 1 }] + } + ); + let expected = State::from([ + Bill { owner: User::Alice, amount: 20, serial: 0 } + ]); + assert_eq!(end, expected); +} + +#[test] +fn spending_same_bill_fails() { + let start = State::from([ + Bill { owner: User::Alice, amount: 40, serial: 0 } + ]); + let end = DigitalCashSystem::next_state( + &start, + &CashTransaction::Transfer { + spends: vec![ + Bill { owner: User::Alice, amount: 40, serial: 0 }, + Bill { owner: User::Alice, amount: 40, serial: 0 } + ], + receives: vec![ + Bill { owner: User::Bob, amount: 20, serial: 1 }, + Bill { owner: User::Bob, amount: 20, serial: 2 }, + Bill { owner: User::Alice, amount: 40, serial: 3 } + ], + } + ); + let expected = State::from([ + Bill { owner: User::Alice, amount: 40, serial: 0 } + ]); + assert_eq!(end, expected); +} + +#[test] +fn spending_more_than_bill_fails() { + let start = State::from([ + Bill { owner: User::Alice, amount: 40, serial: 0 }, + Bill { owner: User::Charlie, amount: 42, serial: 1 } + ]); + let end = DigitalCashSystem::next_state( + &start, + &CashTransaction::Transfer { + spends: vec![ + Bill { owner: User::Alice, amount: 40, serial: 0 }, + Bill { owner: User::Charlie, amount: 42, serial: 1 } + ], + receives: vec![ + Bill { owner: User::Bob, amount: 20, serial: 2 }, + Bill { owner: User::Bob, amount: 20, serial: 3 }, + Bill { owner: User::Alice, amount: 52, serial: 4 } + ], + } + ); + let expected = State::from([ + Bill { owner: User::Alice, amount: 40, serial: 0 }, + Bill { owner: User::Charlie, amount: 42, serial: 1 } + ]); + assert_eq!(end, expected); +} + +#[test] +fn spending_non_existent_bill_fails() { + let start = State::from([ + Bill { owner: User::Alice, amount: 32, serial: 0 }, + ]); + let end = DigitalCashSystem::next_state( + &start, + &CashTransaction::Transfer { + spends: vec![ + Bill { owner: User::Bob, amount: 1000, serial: 32 }, + ], + receives: vec![ + Bill { owner: User::Bob, amount: 1000, serial: 33 } + ] + } + ); + let expected = State::from([ + Bill { owner: User::Alice, amount: 32, serial: 0 }, + ]); + assert_eq!(end, expected); +} + +#[test] +fn spending_from_alice_to_all() { + let start = State::from([ + Bill { owner: User::Alice, amount: 42, serial: 0 } + ]); + let end = DigitalCashSystem::next_state( + &start, + &CashTransaction::Transfer { + spends: vec![ + Bill { owner: User::Alice, amount: 42, serial: 0 } + ], + receives: vec![ + Bill { owner: User::Alice, amount: 10, serial: 1 }, + Bill { owner: User::Bob, amount: 10, serial: 2 }, + Bill { owner: User::Charlie, amount: 10, serial: 3 } + ] + } + ); + let mut expected = State::from([ + Bill { owner: User::Alice, amount: 10, serial: 1 }, + Bill { owner: User::Bob, amount: 10, serial: 2 }, + Bill { owner: User::Charlie, amount: 10, serial: 3 } + ]); + expected.set_serial(4); + assert_eq!(end, expected); +} + +#[test] +fn spending_from_bob_to_all() { + let start = State::from([ + Bill { owner: User::Bob, amount: 42, serial: 0 } + ]); + let end = DigitalCashSystem::next_state( + &start, + &CashTransaction::Transfer { + spends: vec![ + Bill { owner: User::Bob, amount: 42, serial: 0 }, + ], + receives: vec![ + Bill { owner: User::Alice, amount: 10, serial: 1 }, + Bill { owner: User::Bob, amount: 10, serial: 2 }, + Bill { owner: User::Charlie, amount: 22, serial: 3 } + ] + } + ); + let mut expected = State::from([ + Bill { owner: User::Alice, amount: 10, serial: 1 }, + Bill { owner: User::Bob, amount: 10, serial: 2 }, + Bill { owner: User::Charlie, amount: 22, serial: 3 } + ]); + expected.set_serial(4); + assert_eq!(end, expected); +} + +#[test] +fn spending_from_charlie_to_all() { + let mut start = State::from([ + Bill { owner: User::Charlie, amount: 68, serial: 54 }, + Bill { owner: User::Alice, amount: 4000, serial: 58 } + ]); + start.set_serial(59); + let end = DigitalCashSystem::next_state( + &start, + &CashTransaction::Transfer { + spends: vec![ + Bill { owner: User::Charlie, amount: 68, serial: 54 } + ], + receives: vec![ + Bill { owner: User::Alice, amount: 42, serial: 59 }, + Bill { owner: User::Bob, amount: 5, serial: 60 }, + Bill { owner: User::Charlie, amount: 5, serial: 61 } + ] + } + ); + let mut expected = State::from([ + Bill { owner: User::Alice, amount: 4000, serial: 58}, + Bill { owner: User::Alice, amount: 42, serial: 59 }, + Bill { owner: User::Bob, amount: 5, serial: 60 }, + Bill { owner: User::Charlie, amount: 5, serial: 61 } + ]); + expected.set_serial(62); + assert_eq!(end, expected); +}