Browse Source

Use actual margin value for contract setup

Includes validating that the amount the other party commits to is the expected amount based on calculating the counterparty margin out of the CFD values.
Note: We could also remove the lock amount from the messages and use the calculation on both sides - that would simplify things.
no-contract-setup-message
Daniel Karzel 3 years ago
parent
commit
fceb18d98b
No known key found for this signature in database GPG Key ID: 30C3FC2E438ADB6E
  1. 96
      daemon/sqlx-data.json
  2. 102
      daemon/src/db.rs
  3. 14
      daemon/src/maker_cfd_actor.rs
  4. 11
      daemon/src/model/cfd.rs
  5. 10
      daemon/src/setup_contract_actor.rs
  6. 13
      daemon/src/taker_cfd_actor.rs

96
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": {

102
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<Sqlite>,
) -> anyhow::Result<Cfd> {
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<Sqlite>) -> anyhow::Result<Vec<Cfd>> {
// 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;

14
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(
{

11
daemon/src/model/cfd.rs

@ -277,6 +277,17 @@ impl Cfd {
Ok(margin)
}
pub fn calc_counterparty_margin(&self) -> Result<Amount> {
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)))?;

10
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<Output = FinalizedCfd>,
mpsc::UnboundedSender<SetupMsg>,
@ -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,
)

13
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
}
}
}

Loading…
Cancel
Save