hypercall_db_diesel/
ledger_ops.rs1use anyhow::Result;
7use diesel::prelude::*;
8use diesel::sql_types::{Binary, Numeric};
9use hypercall_types::WalletAddress;
10use rust_decimal::Decimal;
11use tracing::debug;
12
13pub 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}