use sn_data_types::{Error, Money, PublicKey, Result, Transfer, TransferId};
use std::collections::HashSet;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Wallet {
id: PublicKey,
balance: Money,
credits: Vec<Transfer>,
debits: Vec<Transfer>,
transfer_ids: HashSet<TransferId>,
}
impl Wallet {
pub fn new(id: PublicKey) -> Self {
Self {
id,
balance: Money::zero(),
credits: vec![],
debits: Default::default(),
transfer_ids: Default::default(),
}
}
pub fn id(&self) -> PublicKey {
self.id
}
pub fn next_debit(&self) -> u64 {
self.debits.len() as u64
}
pub fn balance(&self) -> Money {
self.balance
}
pub fn contains(&self, id: &TransferId) -> bool {
self.transfer_ids.contains(id)
}
pub fn is_sequential(&self, transfer: &Transfer) -> Result<bool> {
let id = transfer.id;
if id.actor != self.id {
Err(Error::from("Wallet operation is non-sequential"))
} else {
match self.debits.last() {
None => Ok(id.counter == 0), Some(previous) => Ok(previous.id.counter + 1 == id.counter),
}
}
}
pub fn credits_since(&self, index: usize) -> Vec<Transfer> {
if self.credits.len() > index {
self.credits.split_at(index).1.to_vec()
} else {
vec![]
}
}
pub fn debits_since(&self, index: usize) -> Vec<Transfer> {
if self.debits.len() > index {
self.debits.split_at(index).1.to_vec()
} else {
vec![]
}
}
pub fn append(&mut self, transfer: Transfer) -> Result<()> {
if self.id == transfer.id.actor {
match self.balance.checked_sub(transfer.amount) {
Some(amount) => self.balance = amount,
None => return Err(Error::from("overflow when subtracting!")),
}
let _ = self.transfer_ids.insert(transfer.id);
self.debits.push(transfer);
Ok(())
} else if self.id == transfer.to {
match self.balance.checked_add(transfer.amount) {
Some(amount) => self.balance = amount,
None => return Err(Error::from("overflow when adding!")),
}
let _ = self.transfer_ids.insert(transfer.id);
self.credits.push(transfer);
Ok(())
} else {
Err(Error::from(format!(
"Transfer does not belong to this wallet({:?}): transfer: {:?}",
self.id, transfer
)))
}
}
#[cfg(feature = "simulated-payouts")]
pub fn simulated_credit(&mut self, transfer: Transfer) {
if self.id == transfer.to {
match self.balance.checked_add(transfer.amount) {
Some(amount) => self.balance = amount,
None => panic!("overflow when adding!"),
}
let _ = self.transfer_ids.insert(transfer.id);
self.credits.push(transfer);
} else {
panic!(
"Credit transfer does not belong to this wallet({:?}): transfer: {:?}",
self.id, transfer
)
}
}
#[cfg(feature = "simulated-payouts")]
pub fn simulated_debit(&mut self, transfer: Transfer) {
if self.id == transfer.id.actor {
match self.balance.checked_sub(transfer.amount) {
Some(amount) => self.balance = amount,
None => panic!("overflow when subtracting!"),
}
let _ = self.transfer_ids.insert(transfer.id);
self.debits.push(transfer);
} else {
panic!(
"Debit transfer does not belong to this wallet({:?}): transfer: {:?}",
self.id, transfer
)
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crdts::Dot;
use sn_data_types::PublicKey;
use threshold_crypto::SecretKey;
use xor_name::XorName;
#[test]
fn appends_credits() -> Result<()> {
let balance = Money::from_nano(10);
let first_credit = Transfer {
id: Dot::new(get_random_pk(), 0),
to: get_random_pk(),
amount: balance,
};
let mut wallet = Wallet::new(first_credit.to);
wallet.append(first_credit.clone())?;
let second_credit = Transfer {
id: Dot::new(get_random_pk(), 0),
to: first_credit.to,
amount: balance,
};
wallet.append(second_credit.clone())?;
let credits = wallet.credits_since(0);
let debits = wallet.debits_since(0);
let is_sequential = wallet.is_sequential(&Transfer {
id: Dot::new(first_credit.to, 0),
to: get_random_pk(),
amount: balance,
});
assert!(wallet.contains(&second_credit.id));
assert_eq!(wallet.balance(), balance.checked_add(balance).unwrap());
assert_eq!(credits.len(), 2);
assert_eq!(credits[1], second_credit);
assert!(debits.is_empty());
assert_eq!(wallet.next_debit(), 0);
assert!(is_sequential.is_ok() && is_sequential?);
Ok(())
}
#[test]
fn appends_debits() -> Result<()> {
let balance = Money::from_nano(10);
let first_credit = Transfer {
id: Dot::new(get_random_pk(), 0),
to: get_random_pk(),
amount: balance,
};
let mut wallet = Wallet::new(first_credit.to);
wallet.append(first_credit.clone())?;
let first_debit = Transfer {
id: Dot::new(first_credit.to, 0),
to: get_random_pk(),
amount: balance,
};
wallet.append(first_debit.clone())?;
let credits = wallet.credits_since(0);
let debits = wallet.debits_since(0);
let is_sequential = wallet.is_sequential(&Transfer {
id: Dot::new(first_credit.to, 1),
to: get_random_pk(),
amount: balance,
});
assert!(wallet.contains(&first_debit.id));
assert_eq!(wallet.balance(), Money::zero());
assert_eq!(debits.len(), 1);
assert_eq!(debits[0], first_debit);
assert_eq!(credits.len(), 1);
assert_eq!(credits[0], first_credit);
assert_eq!(wallet.next_debit(), 1);
assert!(is_sequential.is_ok() && is_sequential?);
Ok(())
}
#[allow(unused)]
fn get_random_xor() -> XorName {
XorName::random()
}
fn get_random_pk() -> PublicKey {
PublicKey::from(SecretKey::random().public_key())
}
}