Skip to main content

hypercall_db_diesel/
ledger_ops.rs

1//! Ledger database operations using Diesel.
2//!
3//! These functions take a connection as a parameter, allowing them to be
4//! called within a transaction alongside other operations (e.g., fill inserts).
5
6use anyhow::Result;
7use diesel::prelude::*;
8use diesel::sql_types::{Binary, Numeric};
9use hypercall_types::WalletAddress;
10use rust_decimal::Decimal;
11use tracing::debug;
12
13/// Apply a Decimal balance delta within a transaction.
14pub fn apply_pnl_decimal_sync(
15    conn: &mut PgConnection,
16    wallet: &WalletAddress,
17    balance_delta: Decimal,
18) -> Result<()> {
19    if balance_delta == Decimal::ZERO {
20        return Ok(());
21    }
22
23    diesel::sql_query(
24        r#"
25        INSERT INTO account_balances (account_address, balance)
26        VALUES ($1, $2)
27        ON CONFLICT (account_address)
28        DO UPDATE SET
29            balance = account_balances.balance + EXCLUDED.balance,
30            updated_at = CURRENT_TIMESTAMP
31        "#,
32    )
33    .bind::<Binary, _>(wallet)
34    .bind::<Numeric, _>(balance_delta)
35    .execute(conn)?;
36
37    debug!(
38        "Ledger projection: apply_pnl_decimal_sync({}, {}) - account_balances adjusted",
39        wallet, balance_delta
40    );
41    Ok(())
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47    use crate::test_helpers::TestDb;
48    use hypercall_types::wallet_address::test_wallet;
49    use rust_decimal_macros::dec;
50
51    fn account_balance(conn: &mut PgConnection, wallet: WalletAddress) -> Decimal {
52        crate::schema::account_balances::table
53            .filter(crate::schema::account_balances::account_address.eq(wallet))
54            .select(crate::schema::account_balances::balance)
55            .first::<Decimal>(conn)
56            .unwrap()
57    }
58
59    #[tokio::test]
60    async fn apply_pnl_nonzero_writes_projection() {
61        let test_db = TestDb::new().await.unwrap();
62        let mut conn = test_db.handler.pool().get().unwrap();
63        let wallet = test_wallet(2);
64
65        apply_pnl_decimal_sync(&mut conn, &wallet, dec!(100)).unwrap();
66
67        assert_eq!(account_balance(&mut conn, wallet), dec!(100));
68    }
69
70    #[tokio::test]
71    async fn apply_pnl_accumulates_projection() {
72        let test_db = TestDb::new().await.unwrap();
73        let mut conn = test_db.handler.pool().get().unwrap();
74        let wallet = test_wallet(3);
75
76        apply_pnl_decimal_sync(&mut conn, &wallet, dec!(50)).unwrap();
77        apply_pnl_decimal_sync(&mut conn, &wallet, dec!(30)).unwrap();
78
79        assert_eq!(account_balance(&mut conn, wallet), dec!(80));
80    }
81
82    #[tokio::test]
83    async fn apply_pnl_zero_is_noop() {
84        let test_db = TestDb::new().await.unwrap();
85        let mut conn = test_db.handler.pool().get().unwrap();
86        let wallet = test_wallet(5);
87
88        apply_pnl_decimal_sync(&mut conn, &wallet, dec!(0)).unwrap();
89    }
90}