use rusqlite::{ffi, params, Connection, Savepoint, Transaction}; use crate::db::accounts; use crate::db::accounts::AccountType; use crate::db::cheques::{Cheque, WireTransferRequest}; use crate::db::external_id::AccountId; use crate::util::*; fn _transfer_securities_from_one_account_to_another( tx: &Savepoint, drawer_account_id: &AccountId, payee_account_id: &AccountId, amount: i64, ) -> CustomResult<( accounts::AccountChangeReceipt, accounts::AccountChangeReceipt, )> { let drawer_receipt = accounts::add_to_account(tx, drawer_account_id, -amount)?; let payee_receipt = accounts::add_to_account(tx, payee_account_id, amount)?; if drawer_receipt.amount < 0 && drawer_receipt.account_type != AccountType::SecurityEmitter { let m = format!( "Drawer of {:} does not have sufficient amount in account.", drawer_account_id ); return Err(Error::InvalidAmount(m)); } if drawer_receipt.security_id != drawer_receipt.security_id { let m = format!( "Incompatible security type between accounts {:} (drawer) and {:} (payee).", drawer_account_id, payee_account_id ); return Err(Error::InvalidAmount(m)); } Ok((drawer_receipt, payee_receipt)) } fn _transfer_cheque_securities( tx: &Savepoint, cheque: &Cheque, account_id: &AccountId, ) -> CustomResult<( accounts::AccountChangeReceipt, accounts::AccountChangeReceipt, )> { _transfer_securities_from_one_account_to_another(tx, &cheque.drawer_account_id, account_id, cheque.amount) } fn _transfer_wire_transfer_securities( tx: &Savepoint, wire_transfer_request: &WireTransferRequest, ) -> CustomResult<( accounts::AccountChangeReceipt, accounts::AccountChangeReceipt, )> { _transfer_securities_from_one_account_to_another(tx, &wire_transfer_request.drawer_account_id, &wire_transfer_request.payee_account_id, wire_transfer_request.amount) } fn _add_wire_transfer_request_to_wire_transfers_log(tx: &Connection, cheque: &WireTransferRequest, drawer_receipt: &accounts::AccountChangeReceipt, payee_receipt: &accounts::AccountChangeReceipt) -> Result<(), Error> { let q = " INSERT INTO cached_wire_transfers( drawer_account_id, drawer_account_version, payee_account_id, payee_account_version, wire_transfer_request_number, amount, underlying_security ) VALUES(?1, ?2, ?3, ?4, ?5, ?6, ?7) "; let params = params![ drawer_receipt.account_id, drawer_receipt.version, payee_receipt.account_id, payee_receipt.version, cheque.amount, cheque.amount, payee_receipt.security_id ]; let ret = tx.execute(q, params).map_err(Error::SqlError); match ret { Ok(_) => Ok(()), Err(Error::SqlError(rusqlite::Error::SqliteFailure( ffi::Error { code: _, extended_code: 2067, }, _, ))) => Err(Error::DoubleSpendingError(format!( "Wire transfer number {} has already been cached.", cheque.amount ))), Err(a) => Err(a), } } fn _add_cheque_to_cashed_cheques_log( tx: &Connection, cheque: &Cheque, drawer_receipt: &accounts::AccountChangeReceipt, payee_receipt: &accounts::AccountChangeReceipt, ) -> Result<(), Error> { let q = " INSERT INTO cashed_cheques( drawer_user_id, drawer_account_id, drawer_account_version, payee_user_id, payee_account_id, payee_account_version, cheque_number, amount, underlying_security ) VALUES(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9) "; let params = params![ cheque.drawer_user_id, cheque.drawer_account_id, drawer_receipt.version, cheque.payee_user_id, payee_receipt.account_id, payee_receipt.version, cheque.cheque_number, cheque.amount, cheque.security_id ]; let ret = tx.execute(q, params).map_err(Error::SqlError); match ret { Ok(_) => Ok(()), Err(Error::SqlError(rusqlite::Error::SqliteFailure( ffi::Error { code: _, extended_code: 2067, }, _, ))) => Err(Error::DoubleSpendingError(format!( "Cheque number {} from user {} has already been cached.", cheque.cheque_number, cheque.drawer_user_id ))), Err(a) => Err(a), } } fn cash_cheque_unsafe( savepoint: &Savepoint, cheque: &Cheque, payee_account_id: &AccountId, ) -> CustomResult<()> { if cheque.amount < 0 { return Err(Error::InvalidAmount(format!( "The cheque {:?} has a negative amount.", cheque ))); } if !accounts::is_owner( &savepoint, &cheque.drawer_account_id, &cheque.drawer_user_id, )? { return Err(Error::InvalidDrawer(format!( "The drawer is the security owner: {:?}", cheque ))); } if !accounts::is_owner(&savepoint, payee_account_id, &cheque.payee_user_id)? { return Err(Error::InvalidPayee(format!( "Trying to cash the cheque {:?} in \ an account {:} that is not owned by the payee.", cheque, payee_account_id ))); } let drawer_account_sec = accounts::get_account_by_id(&savepoint, &cheque.drawer_account_id)?.security().id(); if drawer_account_sec != cheque.security_id{ return Err(Error::InvalidDrawer(format!( "Impossible to use an account containing {:} to emit a cheque in {:}", drawer_account_sec, cheque.security_id ))); } let payee_account_sec = accounts::get_account_by_id(&savepoint, &payee_account_id)?.security().id(); if payee_account_sec != cheque.security_id{ return Err(Error::InvalidPayee(format!( "Impossible to cache a {:} cheque in {:} account .", cheque.security_id, payee_account_sec ))); } let (drawer_receipt, payee_receipt) = _transfer_cheque_securities(&savepoint, cheque, payee_account_id)?; _add_cheque_to_cashed_cheques_log(&savepoint, cheque, &drawer_receipt, &payee_receipt)?; Ok(()) } fn cash_wire_transfer_request_unsafe( savepoint: &Savepoint, wire_transfer: &WireTransferRequest, ) -> CustomResult<()> { // if wire_transfer.amount < 0 { // return Err(Error::InvalidAmount(format!( // "The cheque {:?} has a negative amount.", // wire_transfer // ))); // } let (drawer_receipt, payee_receipt) = _transfer_wire_transfer_securities(&savepoint, wire_transfer)?; _add_wire_transfer_request_to_wire_transfers_log(&savepoint, wire_transfer, &drawer_receipt, &payee_receipt)?; Ok(()) } fn cash_cheque( tx: &mut Transaction, cheque: &Cheque, payee_account_id: &AccountId, ) -> CustomResult<()> { let mut savepoint = tx.savepoint().map_err(Error::SqlError)?; let ret = cash_cheque_unsafe(&savepoint, cheque, payee_account_id); match ret { Err(_) => { savepoint.rollback().map_err(Error::SqlError)?; ret } Ok(_) => savepoint.commit().map_err(Error::SqlError), } } pub fn cash_wire_transfer_request( tx: &mut Transaction, wire_transfer: &WireTransferRequest, ) -> CustomResult<()> { let mut savepoint = tx.savepoint().map_err(Error::SqlError)?; let ret = cash_wire_transfer_request_unsafe(& savepoint, wire_transfer); match ret { Err(_) => { savepoint.rollback().map_err(Error::SqlError)?; ret } Ok(_) => savepoint.commit().map_err(Error::SqlError), } } #[cfg(test)] mod test_utils { use rusqlite::Connection; use crate::db::accounts::{ create_checking_account, create_security_emitter_account, get_account_by_id, }; use crate::db::cheques::{Drawer, Payee}; use crate::db::external_id::{Id, UserId}; use crate::db::test_utils; use crate::migration; use super::*; pub fn setup_conn() -> Connection { let mut conn = Connection::open_in_memory().unwrap(); migration::apply_migrations(&mut conn).unwrap(); return conn; } pub fn setup_security_emitter() -> (Connection, Drawer, Payee, AccountId) { let mut conn = setup_conn(); let tx = conn.transaction().unwrap(); let (users, securities, _accounts) = test_utils::setup_basic_environment(&tx).unwrap(); let sec = securities.get(0).unwrap(); let drawer = users.get(0).unwrap(); let payee = users.get(1).unwrap(); let security_emitter_account = create_security_emitter_account(&tx, &sec.id(), &drawer.id()).unwrap(); let payee_account = create_checking_account(&tx, &sec.id(), &payee.id()).unwrap(); tx.commit().unwrap(); let security_emitter: Drawer = (drawer.id(), security_emitter_account.id(), sec.id()); let payee: Payee = (payee.id(), sec.id()); (conn, security_emitter, payee, payee_account.id()) } pub fn setup_security_emitter_mismatched_accounts() -> (Connection, Drawer, Payee, AccountId) { let mut conn = setup_conn(); let tx = conn.transaction().unwrap(); let (users, securities, _accounts) = test_utils::setup_basic_environment(&tx).unwrap(); let sec_1 = securities.get(0).unwrap(); let sec_2 = securities.get(1).unwrap(); let drawer = users.get(0).unwrap(); let payee = users.get(1).unwrap(); let security_emitter_account = create_security_emitter_account(&tx, &sec_1.id(), &drawer.id()).unwrap(); let payee_account = create_checking_account(&tx, &sec_2.id(), &payee.id()).unwrap(); tx.commit().unwrap(); let security_emitter: Drawer = (drawer.id(), security_emitter_account.id(), sec_1.id()); let payee: Payee = (payee.id(), sec_1.id()); (conn, security_emitter, payee, payee_account.id()) } pub fn create_100_security_units_in_payee_account( tx: &mut Transaction, security_emitter: &Drawer, payee: &Payee, payee_account: &AccountId, ) { let cheque = Cheque::new(security_emitter.clone(), payee.clone(), 100, 99); cash_cheque(tx, &cheque, &payee_account).unwrap(); } fn _create_drawer(payee: Payee, payee_account: AccountId) -> Drawer { (payee.0, payee_account, payee.1) } pub fn setup_basic() -> (Connection, Drawer, Payee, AccountId) { let (mut conn, security_emitter, payee, payee_account) = setup_security_emitter(); let mut tx = conn.transaction().unwrap(); create_100_security_units_in_payee_account(&mut tx, &security_emitter, &payee, &payee_account); let reg_account = create_checking_account(&tx, &security_emitter.2, &security_emitter.0).unwrap(); tx.commit().unwrap(); let drawer: Drawer = _create_drawer(payee, payee_account); let new_payee: Payee = (security_emitter.0, security_emitter.2); return (conn, drawer, new_payee, reg_account.id()); } } #[cfg(test)] mod test_caching_cheques { use rusqlite::Connection; use crate::db::accounts::{ create_checking_account, create_security_emitter_account, get_account_by_id, }; use crate::db::cheques::{Drawer, Payee}; use crate::db::external_id::{Id, UserId}; use crate::db::test_utils; use crate::migration; use super::*; use super::test_utils::*; #[test] fn test_happy_path_currency_emitter() -> CustomResult<()> { // done let (mut conn, security_emitter, payee, payee_account) = setup_security_emitter(); let mut tx = conn.transaction().unwrap(); let cheque = Cheque::new(security_emitter, payee, 1000, 99); cash_cheque(&mut tx, &cheque, &payee_account)?; tx.commit().unwrap(); let tx = conn.transaction().unwrap(); let acc = get_account_by_id(&tx, &payee_account).unwrap(); assert_eq!(acc.raw_amount(), 1000); Ok(()) } #[test] fn test_happy_path_regular_cheque() -> CustomResult<()> { // done let (mut conn, drawer, payee, payee_account) = setup_basic(); let mut tx = conn.transaction().unwrap(); let cheque = Cheque::new(drawer.clone(), payee, 25, 10001); cash_cheque(&mut tx, &cheque, &payee_account)?; let payee_acc = get_account_by_id(&tx, &payee_account).unwrap(); assert_eq!(payee_acc.raw_amount(), 25); let drawer_acc = get_account_by_id(&tx, &drawer.1).unwrap(); assert_eq!(drawer_acc.raw_amount(), 75); Ok(()) } fn test_negative_cheque( conn: &mut Connection, drawer: Drawer, payee: Payee, payee_account: AccountId, ) -> CustomResult<()> { let mut tx = conn.transaction().unwrap(); let cheque = Cheque::new(drawer, payee, -1, 99); let ret = cash_cheque(&mut tx, &cheque, &payee_account); match ret { Err(Error::InvalidAmount(_)) => Ok(()), _ => Err(Error::Testing( "Expected invalid amount error here.".to_string(), )), } } #[test] fn test_unfounded_cheque() -> CustomResult<()> { let (mut conn, drawer, payee, payee_account) = setup_basic(); let mut tx = conn.transaction().unwrap(); let cheque = Cheque::new(drawer, payee, 1_000_000, 99); let ret = cash_cheque(&mut tx, &cheque, &payee_account); match ret { Err(Error::InvalidAmount(_)) => Ok(()), _ => Err(Error::Testing( "Expected invalid amount error here.".to_string(), )), } } #[test] fn test_negative_security_emitter_cheque() -> CustomResult<()> { let (mut conn, security_emitter, payee, payee_account) = setup_security_emitter(); test_negative_cheque(&mut conn, security_emitter, payee, payee_account) } #[test] fn test_negative_regular_cheque() -> CustomResult<()> { let (mut conn, drawer, payee, payee_account) = setup_basic(); test_negative_cheque(&mut conn, drawer, payee, payee_account) } fn _test_double_spend( conn: &mut Connection, drawer: Drawer, payee: Payee, payee_account: AccountId, ) -> CustomResult<()> { let mut tx = conn.transaction().unwrap(); let cheque = Cheque::new(drawer, payee, 10, 99); cash_cheque(&mut tx, &cheque, &payee_account)?; tx.commit().unwrap(); let mut tx = conn.transaction().unwrap(); let ret = cash_cheque(&mut tx, &cheque, &payee_account); match ret { Err(Error::DoubleSpendingError(_)) => Ok(()), _ => Err(Error::Testing( "Expected double spending error here.".to_string(), )), } } #[test] fn test_double_spend_security_emitter_cheque() -> CustomResult<()> { let (mut conn, security_emitter, payee, payee_account) = setup_security_emitter(); _test_double_spend(&mut conn, security_emitter, payee, payee_account) } #[test] fn test_double_spend_regular_emitter_cheque() -> CustomResult<()> { let (mut conn, drawer, payee, payee_account) = setup_basic(); _test_double_spend(&mut conn, drawer, payee, payee_account) } fn _test_invalid_drawer( conn: &mut Connection, drawer: Drawer, payee: Payee, payee_account: AccountId, ) -> CustomResult<()> { let mut tx = conn.transaction().unwrap(); let not_currency_owner_user_id = UserId::from_id(drawer.0.get_id() + 72623); let not_currency_owner: Drawer = (not_currency_owner_user_id, drawer.1, drawer.2); let cheque = Cheque::new(not_currency_owner, payee, 100, 99); let ret = cash_cheque(&mut tx, &cheque, &payee_account); match ret { Err(Error::InvalidDrawer(_)) => Ok(()), _ => Err(Error::Testing("Expected invalid drawer here.".to_string())), } } #[test] fn test_invalid_drawer_security_emitter() -> CustomResult<()> { let (mut conn, security_emitter, payee, payee_account) = setup_security_emitter(); _test_invalid_drawer(&mut conn, security_emitter, payee, payee_account) } #[test] fn test_invalid_drawer_regular_cheque() -> CustomResult<()> { let (mut conn, drawer, payee, payee_account) = setup_basic(); _test_invalid_drawer(&mut conn, drawer, payee, payee_account) } fn test_invalid_payee( conn: &mut Connection, security_emitter: Drawer, payee: Payee, ) -> CustomResult<()> { let mut tx = conn.transaction().unwrap(); let wrong_payee_account = &security_emitter.1; let cheque = Cheque::new(security_emitter.clone(), payee.clone(), 1000, 99); let ret = cash_cheque(&mut tx, &cheque, wrong_payee_account); match ret { Err(Error::InvalidPayee(_)) => Ok(()), _ => Err(Error::Testing("Expected invalid payee here.".to_string())), } } #[test] fn test_invalid_payee_security_emitter_cheque() -> CustomResult<()> { let (mut conn, security_emitter, payee, _payee_account) = setup_security_emitter(); test_invalid_payee(&mut conn, security_emitter, payee) } #[test] fn test_invalid_payee_regular_cheque() -> CustomResult<()> { let (mut conn, drawer, payee, _payee_account) = setup_basic(); test_invalid_payee(&mut conn, drawer, payee) } #[test] fn test_mismatch_securities() -> CustomResult<()>{ let (mut conn, drawer, payee, payee_account) = setup_security_emitter_mismatched_accounts(); let cheque = Cheque::new(drawer.clone(), payee.clone(), 1000, 99); let mut tx = conn.transaction().unwrap(); match cash_cheque(&mut tx, &cheque, & payee_account) { Err(Error::InvalidPayee(_)) => Ok(()), _ => Err(Error::Testing("Expected invalid payee here.".to_string())) } } } #[cfg(test)] mod test_wire_transfers { use crate::db::accounts::get_account_by_id; use super::*; use super::test_utils::*; #[test] fn test_happy_path_currency_emitter() -> CustomResult<()> { let (mut conn, security_emitter, payee, payee_account) = setup_security_emitter(); let mut tx = conn.transaction().unwrap(); // let cheque = Cheque::new(security_emitter, payee, 1000, 99); let wire_transfer = WireTransferRequest::new(security_emitter, &payee_account, 1000, 777);; cash_wire_transfer_request(&mut tx, &wire_transfer)?; tx.commit().unwrap(); let tx = conn.transaction().unwrap(); let acc = get_account_by_id(&tx, &payee_account).unwrap(); assert_eq!(acc.raw_amount(), 1000); Ok(()) } #[test] fn test_happy_path_regular_wire_transfer() -> CustomResult<()> { let (mut conn, drawer, payee, payee_account) = setup_basic(); let mut tx = conn.transaction().unwrap(); let wire_transfer = WireTransferRequest::new(drawer.clone(), &payee_account, 25, 989); cash_wire_transfer_request(&mut tx, &wire_transfer)?; let payee_acc = get_account_by_id(&tx, &payee_account).unwrap(); assert_eq!(payee_acc.raw_amount(), 25); let drawer_acc = get_account_by_id(&tx, &drawer.1).unwrap(); assert_eq!(drawer_acc.raw_amount(), 75); Ok(()) } #[test] fn test_wire_transfer_double_spending() -> CustomResult<()> { let (mut conn, drawer, payee, payee_account) = setup_basic(); let wire_transfer = WireTransferRequest::new(drawer.clone(), &payee_account, 25, 989); let mut tx = conn.transaction().unwrap(); cash_wire_transfer_request(&mut tx, &wire_transfer)?; tx.commit().unwrap(); let mut tx = conn.transaction().unwrap(); let ret = cash_wire_transfer_request(&mut tx, &wire_transfer); match ret { Err(Error::DoubleSpendingError(_)) => Ok(()), _ => Err(Error::Testing("Expected DoubleSpendingError error".to_string())) } } }