diff --git a/daemon/sqlx-data.json b/daemon/sqlx-data.json index 9aab65e..dc97e7a 100644 --- a/daemon/sqlx-data.json +++ b/daemon/sqlx-data.json @@ -152,6 +152,102 @@ "nullable": [] } }, + "a8454a2e4288b58d625f10722c6e23a51d5d17e6bb7af9685bcdbe64546969fa": { + "query": "\n select\n cfds.id as cfd_id,\n orders.uuid as order_id,\n orders.initial_price as price,\n orders.min_quantity as min_quantity,\n orders.max_quantity as max_quantity,\n orders.leverage as leverage,\n orders.trading_pair as trading_pair,\n orders.position as position,\n orders.origin as origin,\n orders.liquidation_price as liquidation_price,\n orders.creation_timestamp as creation_timestamp,\n orders.term as term,\n cfds.quantity_usd as quantity_usd,\n cfd_states.state as state\n from cfds as cfds\n inner join orders as orders on cfds.order_uuid = ?\n inner join cfd_states as cfd_states on cfd_states.cfd_id = cfds.id\n where cfd_states.state in (\n select\n state\n from cfd_states\n where cfd_id = cfds.id\n order by id desc\n limit 1\n )\n ", + "describe": { + "columns": [ + { + "name": "cfd_id", + "ordinal": 0, + "type_info": "Int64" + }, + { + "name": "order_id", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "price", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "min_quantity", + "ordinal": 3, + "type_info": "Text" + }, + { + "name": "max_quantity", + "ordinal": 4, + "type_info": "Text" + }, + { + "name": "leverage", + "ordinal": 5, + "type_info": "Int64" + }, + { + "name": "trading_pair", + "ordinal": 6, + "type_info": "Text" + }, + { + "name": "position", + "ordinal": 7, + "type_info": "Text" + }, + { + "name": "origin", + "ordinal": 8, + "type_info": "Text" + }, + { + "name": "liquidation_price", + "ordinal": 9, + "type_info": "Text" + }, + { + "name": "creation_timestamp", + "ordinal": 10, + "type_info": "Text" + }, + { + "name": "term", + "ordinal": 11, + "type_info": "Text" + }, + { + "name": "quantity_usd", + "ordinal": 12, + "type_info": "Text" + }, + { + "name": "state", + "ordinal": 13, + "type_info": "Text" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + true, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false + ] + } + }, "f3dce76f316212c91cb3402b0cef00f1c9adbef8519c54e9bdbd373aab946209": { "query": "\n select * from orders where uuid = ?;\n ", "describe": { diff --git a/daemon/src/db.rs b/daemon/src/db.rs index 1b0159a..1ce6e8e 100644 --- a/daemon/src/db.rs +++ b/daemon/src/db.rs @@ -241,6 +241,82 @@ async fn load_latest_cfd_state( Ok(latest_cfd_state_in_db) } +pub async fn load_cfd_by_order_id( + order_id: OrderId, + conn: &mut PoolConnection, +) -> anyhow::Result { + let order_uuid = serde_json::to_string(&order_id)?; + + let row = sqlx::query!( + r#" + select + cfds.id as cfd_id, + orders.uuid as order_id, + orders.initial_price as price, + orders.min_quantity as min_quantity, + orders.max_quantity as max_quantity, + orders.leverage as leverage, + orders.trading_pair as trading_pair, + orders.position as position, + orders.origin as origin, + orders.liquidation_price as liquidation_price, + orders.creation_timestamp as creation_timestamp, + orders.term as term, + cfds.quantity_usd as quantity_usd, + cfd_states.state as state + from cfds as cfds + inner join orders as orders on cfds.order_uuid = ? + inner join cfd_states as cfd_states on cfd_states.cfd_id = cfds.id + where cfd_states.state in ( + select + state + from cfd_states + where cfd_id = cfds.id + order by id desc + limit 1 + ) + "#, + order_uuid + ) + .fetch_one(conn) + .await?; + + let order_id = serde_json::from_str(row.order_id.as_str()).unwrap(); + let trading_pair = serde_json::from_str(row.trading_pair.as_str()).unwrap(); + let position: Position = serde_json::from_str(row.position.as_str()).unwrap(); + let price = serde_json::from_str(row.price.as_str()).unwrap(); + let min_quantity = serde_json::from_str(row.min_quantity.as_str()).unwrap(); + let max_quantity = serde_json::from_str(row.max_quantity.as_str()).unwrap(); + let leverage = Leverage(row.leverage.try_into().unwrap()); + let liquidation_price = serde_json::from_str(row.liquidation_price.as_str()).unwrap(); + let creation_timestamp = serde_json::from_str(row.creation_timestamp.as_str()).unwrap(); + let term = serde_json::from_str(row.term.as_str()).unwrap(); + let origin: Origin = serde_json::from_str(row.origin.as_str()).unwrap(); + + let quantity = serde_json::from_str(row.quantity_usd.as_str()).unwrap(); + let latest_state = serde_json::from_str(row.state.as_str()).unwrap(); + + let order = Order { + id: order_id, + trading_pair, + position, + price, + min_quantity, + max_quantity, + leverage, + liquidation_price, + creation_timestamp, + term, + origin, + }; + + Ok(Cfd { + order, + quantity_usd: quantity, + state: latest_state, + }) +} + /// Loads all CFDs with the latest state as the CFD state pub async fn load_all_cfds(conn: &mut PoolConnection) -> anyhow::Result> { // TODO: Could be optimized with something like but not sure it's worth the complexity: @@ -374,6 +450,32 @@ mod tests { assert_eq!(cfd, cfd_from_db) } + #[tokio::test] + async fn test_insert_and_load_cfd_by_order_id() { + let pool = setup_test_db().await; + let mut conn = pool.acquire().await.unwrap(); + + let order = Order::from_default_with_price(Usd(dec!(10000)), Origin::Theirs).unwrap(); + let cfd = Cfd::new( + order.clone(), + Usd(dec!(1000)), + CfdState::PendingTakeRequest { + common: CfdStateCommon { + transition_timestamp: SystemTime::now(), + }, + }, + ); + + let order_id = order.id; + + // the order ahs to exist in the db in order to be able to insert the cfd + insert_order(&order, &mut conn).await.unwrap(); + insert_cfd(cfd.clone(), &mut conn).await.unwrap(); + + let cfd_from_db = load_cfd_by_order_id(order_id, &mut conn).await.unwrap(); + assert_eq!(cfd, cfd_from_db) + } + #[tokio::test] async fn test_insert_new_cfd_state() { let pool = setup_test_db().await; diff --git a/daemon/src/maker_cfd_actor.rs b/daemon/src/maker_cfd_actor.rs index 9bb4c6d..1e37d66 100644 --- a/daemon/src/maker_cfd_actor.rs +++ b/daemon/src/maker_cfd_actor.rs @@ -1,14 +1,12 @@ -use std::time::SystemTime; - -use crate::db::{insert_cfd, insert_order, load_all_cfds, load_order_by_id}; +use crate::db::{insert_cfd, insert_order, load_all_cfds, load_cfd_by_order_id, load_order_by_id}; use crate::model::cfd::{Cfd, CfdState, CfdStateCommon, FinalizedCfd, Order, OrderId}; use crate::model::{TakerId, Usd}; use crate::wallet::Wallet; use crate::wire::SetupMsg; use crate::{maker_cfd_actor, maker_inc_connections_actor, setup_contract_actor}; use bdk::bitcoin::secp256k1::schnorrsig; -use bdk::bitcoin::{self}; use futures::Future; +use std::time::SystemTime; use tokio::sync::{mpsc, watch}; #[allow(clippy::large_enum_variant)] @@ -156,12 +154,10 @@ pub fn new( // Kick-off the CFD protocol let (sk, pk) = crate::keypair::new(&mut rand::thread_rng()); - // TODO: Load correct quantity from DB with order_id - let maker_params = wallet - .build_party_params(bitcoin::Amount::ZERO, pk) - .unwrap(); + let cfd = load_cfd_by_order_id(order_id, &mut conn).await.unwrap(); + let margin = cfd.calc_margin().unwrap(); - let cfd = load_order_by_id(order_id, &mut conn).await.unwrap(); + let maker_params = wallet.build_party_params(margin, pk).unwrap(); let (actor, inbox) = setup_contract_actor::new( { diff --git a/daemon/src/model/cfd.rs b/daemon/src/model/cfd.rs index de09799..fd5617c 100644 --- a/daemon/src/model/cfd.rs +++ b/daemon/src/model/cfd.rs @@ -277,6 +277,17 @@ impl Cfd { Ok(margin) } + pub fn calc_counterparty_margin(&self) -> Result { + let margin = match self.position() { + Position::Buy => calculate_sell_margin(self.order.price, self.quantity_usd)?, + Position::Sell => { + calculate_buy_margin(self.order.price, self.quantity_usd, self.order.leverage)? + } + }; + + Ok(margin) + } + pub fn calc_profit(&self, current_price: Usd) -> Result<(Amount, Usd)> { let profit = calculate_profit(self.order.price, current_price, dec!(0.005), Usd(dec!(0.1)))?; diff --git a/daemon/src/setup_contract_actor.rs b/daemon/src/setup_contract_actor.rs index c1dd6bf..99fa0ca 100644 --- a/daemon/src/setup_contract_actor.rs +++ b/daemon/src/setup_contract_actor.rs @@ -1,4 +1,4 @@ -use crate::model::cfd::{AsBlocks, FinalizedCfd, Order}; +use crate::model::cfd::{AsBlocks, Cfd, FinalizedCfd}; use crate::wire::{AdaptorSignature, Msg0, Msg1, SetupMsg}; use anyhow::{Context, Result}; use bdk::bitcoin::secp256k1::{schnorrsig, SecretKey, Signature, SECP256K1}; @@ -37,7 +37,7 @@ pub fn new( own_params: OwnParams, sk: SecretKey, oracle_pk: schnorrsig::PublicKey, - order: Order, + cfd: Cfd, ) -> ( impl Future, mpsc::UnboundedSender, @@ -66,11 +66,15 @@ pub fn new( AllParams::new(own, own_punish, other, other_punish, own_role) }; + if params.other.lock_amount != cfd.calc_counterparty_margin().unwrap() { + panic!("Sorry, have to panic 😬 - the amounts that the counterparty sent were wrong, expected {} actual {}", cfd.calc_counterparty_margin().unwrap(), params.other.lock_amount) + } + let own_cfd_txs = create_cfd_transactions( (params.maker().clone(), *params.maker_punish()), (params.taker().clone(), *params.taker_punish()), oracle_pk, - order.term.mul_f32(REFUND_THRESHOLD).as_blocks().ceil() as u32, + cfd.order.term.mul_f32(REFUND_THRESHOLD).as_blocks().ceil() as u32, vec![], sk, ) diff --git a/daemon/src/taker_cfd_actor.rs b/daemon/src/taker_cfd_actor.rs index d8b18fb..1f6113e 100644 --- a/daemon/src/taker_cfd_actor.rs +++ b/daemon/src/taker_cfd_actor.rs @@ -1,5 +1,6 @@ use crate::db::{ - insert_cfd, insert_new_cfd_state_by_order_id, insert_order, load_all_cfds, load_order_by_id, + insert_cfd, insert_new_cfd_state_by_order_id, insert_order, load_all_cfds, + load_cfd_by_order_id, load_order_by_id, }; use crate::model::cfd::{Cfd, CfdState, CfdStateCommon, FinalizedCfd, Order, OrderId}; use crate::model::Usd; @@ -7,7 +8,6 @@ use crate::wallet::Wallet; use crate::wire::SetupMsg; use crate::{setup_contract_actor, wire}; use bdk::bitcoin::secp256k1::schnorrsig; -use bdk::bitcoin::{self}; use core::panic; use futures::Future; use std::time::SystemTime; @@ -101,11 +101,10 @@ pub fn new( let (sk, pk) = crate::keypair::new(&mut rand::thread_rng()); - let taker_params = wallet - .build_party_params(bitcoin::Amount::ZERO, pk) // TODO: Load correct quantity from DB - .unwrap(); + let cfd = load_cfd_by_order_id(order_id, &mut conn).await.unwrap(); + let margin = cfd.calc_margin().unwrap(); - let cfd = load_order_by_id(order_id, &mut conn).await.unwrap(); + let taker_params = wallet.build_party_params(margin, pk).unwrap(); let (actor, inbox) = setup_contract_actor::new( { @@ -139,6 +138,8 @@ pub fn new( } Command::CfdSetupCompleted(_finalized_cfd) => { todo!("but what?") + + // Assumption: The maker publishes the CFD on chain } } }