Browse Source

Rollover order boilerplate

Add support for multiple update proposals.
A taker might request to update/change a CFD in different ways, e.g.: propose to settle it or roll over.
In order to support different scenarios we introduce another layer of abstraction.
The rest of the changes creates a boilerplate for the actual rollover protocol.
feature/integration-tests
Philipp Hoenisch 3 years ago
parent
commit
b012aa7a37
No known key found for this signature in database GPG Key ID: E5F8E74C672BC666
  1. 10
      daemon/src/maker.rs
  2. 118
      daemon/src/maker_cfd.rs
  3. 22
      daemon/src/model/cfd.rs
  4. 22
      daemon/src/routes_maker.rs
  5. 14
      daemon/src/routes_taker.rs
  6. 10
      daemon/src/taker.rs
  7. 72
      daemon/src/taker_cfd.rs
  8. 45
      daemon/src/to_sse_event.rs
  9. 5
      daemon/src/wire.rs
  10. 7
      frontend/src/MakerApp.tsx
  11. 15
      frontend/src/components/Types.tsx
  12. 4
      frontend/src/components/cfdtables/CfdTable.tsx

10
daemon/src/maker.rs

@ -1,6 +1,6 @@
use crate::auth::MAKER_USERNAME; use crate::auth::MAKER_USERNAME;
use crate::db::load_all_cfds; use crate::db::load_all_cfds;
use crate::model::cfd::SettlementProposals; use crate::model::cfd::UpdateCfdProposals;
use crate::seed::Seed; use crate::seed::Seed;
use crate::wallet::Wallet; use crate::wallet::Wallet;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
@ -113,8 +113,8 @@ async fn main() -> Result<()> {
let (order_feed_sender, order_feed_receiver) = watch::channel::<Option<Order>>(None); let (order_feed_sender, order_feed_receiver) = watch::channel::<Option<Order>>(None);
let (wallet_feed_sender, wallet_feed_receiver) = watch::channel::<WalletInfo>(wallet_info); let (wallet_feed_sender, wallet_feed_receiver) = watch::channel::<WalletInfo>(wallet_info);
let (settlement_feed_sender, settlement_feed_receiver) = let (update_cfd_feed_sender, update_cfd_feed_receiver) =
watch::channel::<SettlementProposals>(HashMap::new()); watch::channel::<UpdateCfdProposals>(HashMap::new());
let figment = rocket::Config::figment() let figment = rocket::Config::figment()
.merge(("databases.maker.url", data_dir.join("maker.sqlite"))) .merge(("databases.maker.url", data_dir.join("maker.sqlite")))
@ -131,7 +131,7 @@ async fn main() -> Result<()> {
rocket::custom(figment) rocket::custom(figment)
.manage(order_feed_receiver) .manage(order_feed_receiver)
.manage(wallet_feed_receiver) .manage(wallet_feed_receiver)
.manage(settlement_feed_receiver) .manage(update_cfd_feed_receiver)
.manage(auth_password) .manage(auth_password)
.manage(quote_updates) .manage(quote_updates)
.attach(Db::init()) .attach(Db::init())
@ -180,7 +180,7 @@ async fn main() -> Result<()> {
schnorrsig::PublicKey::from_keypair(SECP256K1, &oracle), schnorrsig::PublicKey::from_keypair(SECP256K1, &oracle),
cfd_feed_sender, cfd_feed_sender,
order_feed_sender, order_feed_sender,
settlement_feed_sender, update_cfd_feed_sender,
maker_inc_connections_address.clone(), maker_inc_connections_address.clone(),
monitor_actor_address.clone(), monitor_actor_address.clone(),
oracle_actor_address, oracle_actor_address,

118
daemon/src/maker_cfd.rs

@ -6,11 +6,12 @@ use crate::db::{
use crate::maker_inc_connections::TakerCommand; use crate::maker_inc_connections::TakerCommand;
use crate::model::cfd::{ use crate::model::cfd::{
Cfd, CfdState, CfdStateChangeEvent, CfdStateCommon, Dlc, Order, OrderId, Origin, Role, Cfd, CfdState, CfdStateChangeEvent, CfdStateCommon, Dlc, Order, OrderId, Origin, Role,
SettlementKind, SettlementProposal, SettlementProposals, RollOverProposal, SettlementKind, SettlementProposal, UpdateCfdProposal, UpdateCfdProposals,
}; };
use crate::model::{OracleEventId, TakerId, Usd}; use crate::model::{OracleEventId, TakerId, Usd};
use crate::monitor::MonitorParams; use crate::monitor::MonitorParams;
use crate::wallet::Wallet; use crate::wallet::Wallet;
use crate::wire::TakerToMaker;
use crate::{maker_inc_connections, monitor, oracle, setup_contract, wire}; use crate::{maker_inc_connections, monitor, oracle, setup_contract, wire};
use anyhow::{Context as _, Result}; use anyhow::{Context as _, Result};
use async_trait::async_trait; use async_trait::async_trait;
@ -43,6 +44,14 @@ pub struct RejectSettlement {
pub order_id: OrderId, pub order_id: OrderId,
} }
pub struct AcceptRollOver {
pub order_id: OrderId,
}
pub struct RejectRollOver {
pub order_id: OrderId,
}
pub struct NewOrder { pub struct NewOrder {
pub price: Usd, pub price: Usd,
pub min_quantity: Usd, pub min_quantity: Usd,
@ -69,14 +78,14 @@ pub struct Actor {
oracle_pk: schnorrsig::PublicKey, oracle_pk: schnorrsig::PublicKey,
cfd_feed_actor_inbox: watch::Sender<Vec<Cfd>>, cfd_feed_actor_inbox: watch::Sender<Vec<Cfd>>,
order_feed_sender: watch::Sender<Option<Order>>, order_feed_sender: watch::Sender<Option<Order>>,
settlements_feed_sender: watch::Sender<SettlementProposals>, update_cfd_feed_sender: watch::Sender<UpdateCfdProposals>,
takers: Address<maker_inc_connections::Actor>, takers: Address<maker_inc_connections::Actor>,
current_order_id: Option<OrderId>, current_order_id: Option<OrderId>,
monitor_actor: Address<monitor::Actor<Actor>>, monitor_actor: Address<monitor::Actor<Actor>>,
setup_state: SetupState, setup_state: SetupState,
latest_announcements: Option<BTreeMap<OracleEventId, oracle::Announcement>>, latest_announcements: Option<BTreeMap<OracleEventId, oracle::Announcement>>,
oracle_actor: Address<oracle::Actor<Actor, monitor::Actor<Actor>>>, oracle_actor: Address<oracle::Actor<Actor, monitor::Actor<Actor>>>,
current_settlement_proposals: SettlementProposals, current_pending_proposals: UpdateCfdProposals,
} }
enum SetupState { enum SetupState {
@ -95,7 +104,7 @@ impl Actor {
oracle_pk: schnorrsig::PublicKey, oracle_pk: schnorrsig::PublicKey,
cfd_feed_actor_inbox: watch::Sender<Vec<Cfd>>, cfd_feed_actor_inbox: watch::Sender<Vec<Cfd>>,
order_feed_sender: watch::Sender<Option<Order>>, order_feed_sender: watch::Sender<Option<Order>>,
settlements_feed_sender: watch::Sender<SettlementProposals>, update_cfd_feed_sender: watch::Sender<UpdateCfdProposals>,
takers: Address<maker_inc_connections::Actor>, takers: Address<maker_inc_connections::Actor>,
monitor_actor: Address<monitor::Actor<Actor>>, monitor_actor: Address<monitor::Actor<Actor>>,
oracle_actor: Address<oracle::Actor<Actor, monitor::Actor<Actor>>>, oracle_actor: Address<oracle::Actor<Actor, monitor::Actor<Actor>>>,
@ -106,21 +115,21 @@ impl Actor {
oracle_pk, oracle_pk,
cfd_feed_actor_inbox, cfd_feed_actor_inbox,
order_feed_sender, order_feed_sender,
settlements_feed_sender, update_cfd_feed_sender,
takers, takers,
current_order_id: None, current_order_id: None,
monitor_actor, monitor_actor,
setup_state: SetupState::None, setup_state: SetupState::None,
latest_announcements: None, latest_announcements: None,
oracle_actor, oracle_actor,
current_settlement_proposals: HashMap::new(), current_pending_proposals: HashMap::new(),
} }
} }
fn send_current_settlement_proposals(&self) -> Result<()> { fn send_pending_proposals(&self) -> Result<()> {
Ok(self Ok(self
.settlements_feed_sender .update_cfd_feed_sender
.send(self.current_settlement_proposals.clone())?) .send(self.current_pending_proposals.clone())?)
} }
async fn handle_new_order( async fn handle_new_order(
@ -189,9 +198,32 @@ impl Actor {
"Received settlement proposal from the taker: {:?}", "Received settlement proposal from the taker: {:?}",
proposal proposal
); );
self.current_settlement_proposals self.current_pending_proposals.insert(
.insert(proposal.order_id, (proposal, SettlementKind::Incoming)); proposal.order_id,
self.send_current_settlement_proposals()?; UpdateCfdProposal::Settlement {
proposal,
direction: SettlementKind::Incoming,
},
);
self.send_pending_proposals()?;
Ok(())
}
async fn handle_propose_roll_over(&mut self, proposal: RollOverProposal) -> Result<()> {
tracing::info!(
"Received proposal from the taker: {:?} to roll over order {}",
proposal,
proposal.order_id
);
self.current_pending_proposals.insert(
proposal.order_id,
UpdateCfdProposal::RollOverProposal {
proposal,
direction: SettlementKind::Incoming,
},
);
self.send_pending_proposals()?;
Ok(()) Ok(())
} }
@ -487,10 +519,10 @@ impl Actor {
tracing::debug!(%order_id, "Maker accepts a settlement proposal" ); tracing::debug!(%order_id, "Maker accepts a settlement proposal" );
// TODO: Initiate the settlement // TODO: Initiate the settlement
self.current_settlement_proposals self.current_pending_proposals
.remove(&order_id) .remove(&order_id)
.context("Could not find proposal for given order id")?; .context("Could not find proposal for given order id")?;
self.send_current_settlement_proposals()?; self.send_pending_proposals()?;
Ok(()) Ok(())
} }
@ -499,10 +531,33 @@ impl Actor {
// TODO: Handle rejection offer: // TODO: Handle rejection offer:
// - notify the taker that the settlement was rejected // - notify the taker that the settlement was rejected
self.current_settlement_proposals self.current_pending_proposals
.remove(&order_id) .remove(&order_id)
.context("Could not find proposal for given order id")?; .context("Could not find proposal for given order id")?;
self.send_current_settlement_proposals()?; self.send_pending_proposals()?;
Ok(())
}
async fn handle_accept_roll_over(&mut self, order_id: OrderId) -> Result<()> {
tracing::debug!(%order_id, "Maker accepts a roll over proposal" );
// TODO: Initiate the roll over logic
self.current_pending_proposals
.remove(&order_id)
.context("Could not find roll over proposal for given order id")?;
self.send_pending_proposals()?;
Ok(())
}
async fn handle_reject_roll_over(&mut self, order_id: OrderId) -> Result<()> {
tracing::debug!(%order_id, "Maker rejects a roll over proposal" );
// TODO: Handle rejection and notify the taker that the roll over was rejected
self.current_pending_proposals
.remove(&order_id)
.context("Could not find roll over proposal for given order id")?;
self.send_pending_proposals()?;
Ok(()) Ok(())
} }
@ -588,6 +643,20 @@ impl Handler<RejectSettlement> for Actor {
} }
} }
#[async_trait]
impl Handler<AcceptRollOver> for Actor {
async fn handle(&mut self, msg: AcceptRollOver, _ctx: &mut Context<Self>) {
log_error!(self.handle_accept_roll_over(msg.order_id))
}
}
#[async_trait]
impl Handler<RejectRollOver> for Actor {
async fn handle(&mut self, msg: RejectRollOver, _ctx: &mut Context<Self>) {
log_error!(self.handle_reject_roll_over(msg.order_id))
}
}
#[async_trait] #[async_trait]
impl Handler<Commit> for Actor { impl Handler<Commit> for Actor {
async fn handle(&mut self, msg: Commit, _ctx: &mut Context<Self>) { async fn handle(&mut self, msg: Commit, _ctx: &mut Context<Self>) {
@ -662,6 +731,15 @@ impl Handler<TakerStreamMessage> for Actor {
wire::TakerToMaker::Protocol(msg) => { wire::TakerToMaker::Protocol(msg) => {
log_error!(self.handle_inc_protocol_msg(taker, msg)) log_error!(self.handle_inc_protocol_msg(taker, msg))
} }
TakerToMaker::ProposeRollOver {
order_id,
timestamp,
} => {
log_error!(self.handle_propose_roll_over(RollOverProposal {
order_id,
timestamp,
}))
}
} }
KeepRunning::Yes KeepRunning::Yes
@ -714,6 +792,14 @@ impl Message for RejectSettlement {
type Result = (); type Result = ();
} }
impl Message for AcceptRollOver {
type Result = ();
}
impl Message for RejectRollOver {
type Result = ();
}
// this signature is a bit different because we use `Address::attach_stream` // this signature is a bit different because we use `Address::attach_stream`
impl Message for TakerStreamMessage { impl Message for TakerStreamMessage {
type Result = KeepRunning; type Result = KeepRunning;

22
daemon/src/model/cfd.rs

@ -345,6 +345,18 @@ impl fmt::Display for CfdState {
} }
} }
#[derive(Debug, Clone)]
pub enum UpdateCfdProposal {
Settlement {
proposal: SettlementProposal,
direction: SettlementKind,
},
RollOverProposal {
proposal: RollOverProposal,
direction: SettlementKind,
},
}
/// Proposed collaborative settlement /// Proposed collaborative settlement
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SettlementProposal { pub struct SettlementProposal {
@ -354,6 +366,14 @@ pub struct SettlementProposal {
pub maker: Amount, pub maker: Amount,
} }
/// Proposal to roll over over a fixed length.
/// The length of the roll over is defined by the maker.
#[derive(Debug, Clone)]
pub struct RollOverProposal {
pub order_id: OrderId,
pub timestamp: SystemTime,
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[allow(dead_code)] // Variants (for now) used by different binaries. #[allow(dead_code)] // Variants (for now) used by different binaries.
pub enum SettlementKind { pub enum SettlementKind {
@ -361,7 +381,7 @@ pub enum SettlementKind {
Outgoing, Outgoing,
} }
pub type SettlementProposals = HashMap<OrderId, (SettlementProposal, SettlementKind)>; pub type UpdateCfdProposals = HashMap<OrderId, UpdateCfdProposal>;
/// Represents a cfd (including state) /// Represents a cfd (including state)
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]

22
daemon/src/routes_maker.rs

@ -1,5 +1,5 @@
use crate::auth::Authenticated; use crate::auth::Authenticated;
use crate::model::cfd::{Cfd, Order, OrderId, Role, SettlementProposals}; use crate::model::cfd::{Cfd, Order, OrderId, Role, UpdateCfdProposals};
use crate::model::{Usd, WalletInfo}; use crate::model::{Usd, WalletInfo};
use crate::routes::EmbeddedFileExt; use crate::routes::EmbeddedFileExt;
use crate::to_sse_event::{CfdAction, CfdsWithAuxData, ToSseEvent}; use crate::to_sse_event::{CfdAction, CfdsWithAuxData, ToSseEvent};
@ -24,7 +24,7 @@ pub async fn maker_feed(
rx_order: &State<watch::Receiver<Option<Order>>>, rx_order: &State<watch::Receiver<Option<Order>>>,
rx_wallet: &State<watch::Receiver<WalletInfo>>, rx_wallet: &State<watch::Receiver<WalletInfo>>,
rx_quote: &State<watch::Receiver<bitmex_price_feed::Quote>>, rx_quote: &State<watch::Receiver<bitmex_price_feed::Quote>>,
rx_settlements: &State<watch::Receiver<SettlementProposals>>, rx_settlements: &State<watch::Receiver<UpdateCfdProposals>>,
_auth: Authenticated, _auth: Authenticated,
) -> EventStream![] { ) -> EventStream![] {
let mut rx_cfds = rx_cfds.inner().clone(); let mut rx_cfds = rx_cfds.inner().clone();
@ -149,6 +149,19 @@ pub async fn post_cfd_action(
.await .await
.expect("actor to always be available"); .expect("actor to always be available");
} }
CfdAction::AcceptRollOver => {
cfd_actor_address
.do_send_async(maker_cfd::AcceptRollOver { order_id: id })
.await
.expect("actor to always be available");
}
CfdAction::RejectRollOver => {
cfd_actor_address
.do_send_async(maker_cfd::RejectRollOver { order_id: id })
.await
.expect("actor to always be available");
}
CfdAction::Commit => { CfdAction::Commit => {
cfd_actor_address cfd_actor_address
.do_send_async(maker_cfd::Commit { order_id: id }) .do_send_async(maker_cfd::Commit { order_id: id })
@ -160,6 +173,11 @@ pub async fn post_cfd_action(
"Collaborative settlement can only be triggered by taker".to_string(), "Collaborative settlement can only be triggered by taker".to_string(),
))); )));
} }
CfdAction::RollOver => {
return Err(status::BadRequest(Some(
"RollOver proposal can only be triggered by taker".to_string(),
)));
}
} }
Ok(status::Accepted(None)) Ok(status::Accepted(None))

14
daemon/src/routes_taker.rs

@ -1,4 +1,4 @@
use crate::model::cfd::{calculate_buy_margin, Cfd, Order, OrderId, Role, SettlementProposals}; use crate::model::cfd::{calculate_buy_margin, Cfd, Order, OrderId, Role, UpdateCfdProposals};
use crate::model::{Leverage, Usd, WalletInfo}; use crate::model::{Leverage, Usd, WalletInfo};
use crate::routes::EmbeddedFileExt; use crate::routes::EmbeddedFileExt;
use crate::to_sse_event::{CfdAction, CfdsWithAuxData, ToSseEvent}; use crate::to_sse_event::{CfdAction, CfdsWithAuxData, ToSseEvent};
@ -23,7 +23,7 @@ pub async fn feed(
rx_order: &State<watch::Receiver<Option<Order>>>, rx_order: &State<watch::Receiver<Option<Order>>>,
rx_wallet: &State<watch::Receiver<WalletInfo>>, rx_wallet: &State<watch::Receiver<WalletInfo>>,
rx_quote: &State<watch::Receiver<bitmex_price_feed::Quote>>, rx_quote: &State<watch::Receiver<bitmex_price_feed::Quote>>,
rx_settlements: &State<watch::Receiver<SettlementProposals>>, rx_settlements: &State<watch::Receiver<UpdateCfdProposals>>,
) -> EventStream![] { ) -> EventStream![] {
let mut rx_cfds = rx_cfds.inner().clone(); let mut rx_cfds = rx_cfds.inner().clone();
let mut rx_order = rx_order.inner().clone(); let mut rx_order = rx_order.inner().clone();
@ -100,7 +100,9 @@ pub async fn post_cfd_action(
CfdAction::AcceptOrder CfdAction::AcceptOrder
| CfdAction::RejectOrder | CfdAction::RejectOrder
| CfdAction::AcceptSettlement | CfdAction::AcceptSettlement
| CfdAction::RejectSettlement => { | CfdAction::RejectSettlement
| CfdAction::AcceptRollOver
| CfdAction::RejectRollOver => {
return Err(status::BadRequest(None)); return Err(status::BadRequest(None));
} }
CfdAction::Commit => { CfdAction::Commit => {
@ -119,6 +121,12 @@ pub async fn post_cfd_action(
.await .await
.expect("actor to always be available"); .expect("actor to always be available");
} }
CfdAction::RollOver => {
cfd_actor_address
.do_send_async(taker_cfd::ProposeRollOver { order_id: id })
.await
.expect("actor to always be available");
}
} }
Ok(status::Accepted(None)) Ok(status::Accepted(None))

10
daemon/src/taker.rs

@ -1,5 +1,5 @@
use crate::db::load_all_cfds; use crate::db::load_all_cfds;
use crate::model::cfd::SettlementProposals; use crate::model::cfd::UpdateCfdProposals;
use crate::model::WalletInfo; use crate::model::WalletInfo;
use crate::wallet::Wallet; use crate::wallet::Wallet;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
@ -108,8 +108,8 @@ async fn main() -> Result<()> {
let (order_feed_sender, order_feed_receiver) = watch::channel::<Option<Order>>(None); let (order_feed_sender, order_feed_receiver) = watch::channel::<Option<Order>>(None);
let (wallet_feed_sender, wallet_feed_receiver) = watch::channel::<WalletInfo>(wallet_info); let (wallet_feed_sender, wallet_feed_receiver) = watch::channel::<WalletInfo>(wallet_info);
let (settlement_feed_sender, settlement_feed_receiver) = let (update_cfd_feed_sender, update_feed_receiver) =
watch::channel::<SettlementProposals>(HashMap::new()); watch::channel::<UpdateCfdProposals>(HashMap::new());
let (read, write) = loop { let (read, write) = loop {
let socket = tokio::net::TcpSocket::new_v4()?; let socket = tokio::net::TcpSocket::new_v4()?;
@ -134,7 +134,7 @@ async fn main() -> Result<()> {
rocket::custom(figment) rocket::custom(figment)
.manage(order_feed_receiver) .manage(order_feed_receiver)
.manage(wallet_feed_receiver) .manage(wallet_feed_receiver)
.manage(settlement_feed_receiver) .manage(update_feed_receiver)
.manage(quote_updates) .manage(quote_updates)
.attach(Db::init()) .attach(Db::init())
.attach(AdHoc::try_on_ignite( .attach(AdHoc::try_on_ignite(
@ -182,7 +182,7 @@ async fn main() -> Result<()> {
schnorrsig::PublicKey::from_keypair(SECP256K1, &oracle), schnorrsig::PublicKey::from_keypair(SECP256K1, &oracle),
cfd_feed_sender, cfd_feed_sender,
order_feed_sender, order_feed_sender,
settlement_feed_sender, update_cfd_feed_sender,
send_to_maker, send_to_maker,
monitor_actor_address.clone(), monitor_actor_address.clone(),
oracle_actor_address, oracle_actor_address,

72
daemon/src/taker_cfd.rs

@ -5,7 +5,7 @@ use crate::db::{
}; };
use crate::model::cfd::{ use crate::model::cfd::{
Cfd, CfdState, CfdStateChangeEvent, CfdStateCommon, Dlc, Order, OrderId, Origin, Role, Cfd, CfdState, CfdStateChangeEvent, CfdStateCommon, Dlc, Order, OrderId, Origin, Role,
SettlementKind, SettlementProposals, RollOverProposal, SettlementKind, UpdateCfdProposal, UpdateCfdProposals,
}; };
use crate::model::{OracleEventId, Usd}; use crate::model::{OracleEventId, Usd};
use crate::monitor::{self, MonitorParams}; use crate::monitor::{self, MonitorParams};
@ -33,6 +33,10 @@ pub struct ProposeSettlement {
pub current_price: Usd, pub current_price: Usd,
} }
pub struct ProposeRollOver {
pub order_id: OrderId,
}
pub struct MakerStreamMessage { pub struct MakerStreamMessage {
pub item: Result<wire::MakerToTaker>, pub item: Result<wire::MakerToTaker>,
} }
@ -59,13 +63,13 @@ pub struct Actor {
oracle_pk: schnorrsig::PublicKey, oracle_pk: schnorrsig::PublicKey,
cfd_feed_actor_inbox: watch::Sender<Vec<Cfd>>, cfd_feed_actor_inbox: watch::Sender<Vec<Cfd>>,
order_feed_actor_inbox: watch::Sender<Option<Order>>, order_feed_actor_inbox: watch::Sender<Option<Order>>,
settlements_feed_sender: watch::Sender<SettlementProposals>, update_cfd_feed_sender: watch::Sender<UpdateCfdProposals>,
send_to_maker: Address<send_to_socket::Actor<wire::TakerToMaker>>, send_to_maker: Address<send_to_socket::Actor<wire::TakerToMaker>>,
monitor_actor: Address<monitor::Actor<Actor>>, monitor_actor: Address<monitor::Actor<Actor>>,
setup_state: SetupState, setup_state: SetupState,
latest_announcements: Option<BTreeMap<OracleEventId, oracle::Announcement>>, latest_announcements: Option<BTreeMap<OracleEventId, oracle::Announcement>>,
oracle_actor: Address<oracle::Actor<Actor, monitor::Actor<Actor>>>, oracle_actor: Address<oracle::Actor<Actor, monitor::Actor<Actor>>>,
current_settlement_proposals: SettlementProposals, current_pending_proposals: UpdateCfdProposals,
} }
impl Actor { impl Actor {
@ -76,7 +80,7 @@ impl Actor {
oracle_pk: schnorrsig::PublicKey, oracle_pk: schnorrsig::PublicKey,
cfd_feed_actor_inbox: watch::Sender<Vec<Cfd>>, cfd_feed_actor_inbox: watch::Sender<Vec<Cfd>>,
order_feed_actor_inbox: watch::Sender<Option<Order>>, order_feed_actor_inbox: watch::Sender<Option<Order>>,
settlements_feed_sender: watch::Sender<SettlementProposals>, update_cfd_feed_sender: watch::Sender<UpdateCfdProposals>,
send_to_maker: Address<send_to_socket::Actor<wire::TakerToMaker>>, send_to_maker: Address<send_to_socket::Actor<wire::TakerToMaker>>,
monitor_actor: Address<monitor::Actor<Actor>>, monitor_actor: Address<monitor::Actor<Actor>>,
oracle_actor: Address<oracle::Actor<Actor, monitor::Actor<Actor>>>, oracle_actor: Address<oracle::Actor<Actor, monitor::Actor<Actor>>>,
@ -87,20 +91,20 @@ impl Actor {
oracle_pk, oracle_pk,
cfd_feed_actor_inbox, cfd_feed_actor_inbox,
order_feed_actor_inbox, order_feed_actor_inbox,
settlements_feed_sender, update_cfd_feed_sender,
send_to_maker, send_to_maker,
monitor_actor, monitor_actor,
setup_state: SetupState::None, setup_state: SetupState::None,
oracle_actor, oracle_actor,
latest_announcements: None, latest_announcements: None,
current_settlement_proposals: HashMap::new(), current_pending_proposals: HashMap::new(),
} }
} }
fn send_current_settlement_proposals(&self) -> Result<()> { fn send_pending_update_proposals(&self) -> Result<()> {
Ok(self Ok(self
.settlements_feed_sender .update_cfd_feed_sender
.send(self.current_settlement_proposals.clone())?) .send(self.current_pending_proposals.clone())?)
} }
async fn handle_take_offer(&mut self, order_id: OrderId, quantity: Usd) -> Result<()> { async fn handle_take_offer(&mut self, order_id: OrderId, quantity: Usd) -> Result<()> {
@ -142,7 +146,7 @@ impl Actor {
let proposal = cfd.calculate_settlement(current_price)?; let proposal = cfd.calculate_settlement(current_price)?;
if self if self
.current_settlement_proposals .current_pending_proposals
.contains_key(&proposal.order_id) .contains_key(&proposal.order_id)
{ {
anyhow::bail!( anyhow::bail!(
@ -151,11 +155,14 @@ impl Actor {
) )
} }
self.current_settlement_proposals.insert( self.current_pending_proposals.insert(
proposal.order_id, proposal.order_id,
(proposal.clone(), SettlementKind::Outgoing), UpdateCfdProposal::Settlement {
proposal: proposal.clone(),
direction: SettlementKind::Outgoing,
},
); );
self.send_current_settlement_proposals()?; self.send_pending_update_proposals()?;
self.send_to_maker self.send_to_maker
.do_send_async(wire::TakerToMaker::ProposeSettlement { .do_send_async(wire::TakerToMaker::ProposeSettlement {
@ -168,6 +175,34 @@ impl Actor {
Ok(()) Ok(())
} }
async fn handle_propose_roll_over(&mut self, order_id: OrderId) -> Result<()> {
if self.current_pending_proposals.contains_key(&order_id) {
anyhow::bail!("An update for order id {} is already in progress", order_id)
}
let proposal = RollOverProposal {
order_id,
timestamp: SystemTime::now(),
};
self.current_pending_proposals.insert(
proposal.order_id,
UpdateCfdProposal::RollOverProposal {
proposal: proposal.clone(),
direction: SettlementKind::Outgoing,
},
);
self.send_pending_update_proposals()?;
self.send_to_maker
.do_send_async(wire::TakerToMaker::ProposeRollOver {
order_id: proposal.order_id,
timestamp: proposal.timestamp,
})
.await?;
Ok(())
}
async fn handle_new_order(&mut self, order: Option<Order>) -> Result<()> { async fn handle_new_order(&mut self, order: Option<Order>) -> Result<()> {
match order { match order {
Some(mut order) => { Some(mut order) => {
@ -432,6 +467,13 @@ impl Handler<ProposeSettlement> for Actor {
} }
} }
#[async_trait]
impl Handler<ProposeRollOver> for Actor {
async fn handle(&mut self, msg: ProposeRollOver, _ctx: &mut Context<Self>) {
log_error!(self.handle_propose_roll_over(msg.order_id));
}
}
#[async_trait] #[async_trait]
impl Handler<MakerStreamMessage> for Actor { impl Handler<MakerStreamMessage> for Actor {
async fn handle( async fn handle(
@ -510,6 +552,10 @@ impl Message for ProposeSettlement {
type Result = (); type Result = ();
} }
impl Message for ProposeRollOver {
type Result = ();
}
// this signature is a bit different because we use `Address::attach_stream` // this signature is a bit different because we use `Address::attach_stream`
impl Message for MakerStreamMessage { impl Message for MakerStreamMessage {
type Result = KeepRunning; type Result = KeepRunning;

45
daemon/src/to_sse_event.rs

@ -1,4 +1,4 @@
use crate::model::cfd::{OrderId, Role, SettlementKind, SettlementProposals}; use crate::model::cfd::{OrderId, Role, SettlementKind, UpdateCfdProposal, UpdateCfdProposals};
use crate::model::{Leverage, Position, TradingPair, Usd}; use crate::model::{Leverage, Position, TradingPair, Usd};
use crate::{bitmex_price_feed, model}; use crate::{bitmex_price_feed, model};
use bdk::bitcoin::{Amount, SignedAmount}; use bdk::bitcoin::{Amount, SignedAmount};
@ -42,6 +42,9 @@ pub enum CfdAction {
Settle, Settle,
AcceptSettlement, AcceptSettlement,
RejectSettlement, RejectSettlement,
RollOver,
AcceptRollOver,
RejectRollOver,
} }
impl<'v> FromParam<'v> for CfdAction { impl<'v> FromParam<'v> for CfdAction {
@ -66,6 +69,8 @@ pub enum CfdState {
OpenCommitted, OpenCommitted,
IncomingSettlementProposal, IncomingSettlementProposal,
OutgoingSettlementProposal, OutgoingSettlementProposal,
IncomingRollOverProposal,
OutgoingRollOverProposal,
MustRefund, MustRefund,
Refunded, Refunded,
SetupFailed, SetupFailed,
@ -100,14 +105,14 @@ pub trait ToSseEvent {
pub struct CfdsWithAuxData { pub struct CfdsWithAuxData {
pub cfds: Vec<model::cfd::Cfd>, pub cfds: Vec<model::cfd::Cfd>,
pub current_price: Usd, pub current_price: Usd,
pub settlement_proposals: SettlementProposals, pub pending_proposals: UpdateCfdProposals,
} }
impl CfdsWithAuxData { impl CfdsWithAuxData {
pub fn new( pub fn new(
rx_cfds: &watch::Receiver<Vec<model::cfd::Cfd>>, rx_cfds: &watch::Receiver<Vec<model::cfd::Cfd>>,
rx_quote: &watch::Receiver<bitmex_price_feed::Quote>, rx_quote: &watch::Receiver<bitmex_price_feed::Quote>,
rx_settlement: &watch::Receiver<SettlementProposals>, rx_updates: &watch::Receiver<UpdateCfdProposals>,
role: Role, role: Role,
) -> Self { ) -> Self {
let quote = rx_quote.borrow().clone(); let quote = rx_quote.borrow().clone();
@ -116,21 +121,14 @@ impl CfdsWithAuxData {
Role::Taker => quote.for_taker(), Role::Taker => quote.for_taker(),
}; };
let settlement_proposals = rx_settlement.borrow().clone(); let pending_proposals = rx_updates.borrow().clone();
CfdsWithAuxData { CfdsWithAuxData {
cfds: rx_cfds.borrow().clone(), cfds: rx_cfds.borrow().clone(),
current_price, current_price,
settlement_proposals, pending_proposals,
} }
} }
/// Check whether given CFD has any active settlement proposals
fn settlement_proposal_status(&self, cfd: &model::cfd::Cfd) -> Option<SettlementKind> {
self.settlement_proposals
.get(&cfd.order.id)
.map(|(_, kind)| kind.clone())
}
} }
impl ToSseEvent for CfdsWithAuxData { impl ToSseEvent for CfdsWithAuxData {
@ -151,7 +149,8 @@ impl ToSseEvent for CfdsWithAuxData {
(SignedAmount::ZERO, Decimal::ZERO.into()) (SignedAmount::ZERO, Decimal::ZERO.into())
}); });
let state = to_cfd_state(&cfd.state, self.settlement_proposal_status(cfd)); let pending_proposal = self.pending_proposals.get(&cfd.order.id);
let state = to_cfd_state(&cfd.state, pending_proposal);
Cfd { Cfd {
order_id: cfd.order.id, order_id: cfd.order.id,
@ -228,11 +227,17 @@ impl ToSseEvent for model::WalletInfo {
fn to_cfd_state( fn to_cfd_state(
cfd_state: &model::cfd::CfdState, cfd_state: &model::cfd::CfdState,
proposal_status: Option<SettlementKind>, proposal_status: Option<&UpdateCfdProposal>,
) -> CfdState { ) -> CfdState {
match proposal_status { match proposal_status {
Some(SettlementKind::Incoming) => CfdState::IncomingSettlementProposal, Some(UpdateCfdProposal::Settlement {
Some(SettlementKind::Outgoing) => CfdState::OutgoingSettlementProposal, direction: SettlementKind::Outgoing,
..
}) => CfdState::OutgoingSettlementProposal,
Some(UpdateCfdProposal::Settlement {
direction: SettlementKind::Incoming,
..
}) => CfdState::IncomingSettlementProposal,
None => match cfd_state { None => match cfd_state {
model::cfd::CfdState::OutgoingOrderRequest { .. } => CfdState::OutgoingOrderRequest, model::cfd::CfdState::OutgoingOrderRequest { .. } => CfdState::OutgoingOrderRequest,
model::cfd::CfdState::IncomingOrderRequest { .. } => CfdState::IncomingOrderRequest, model::cfd::CfdState::IncomingOrderRequest { .. } => CfdState::IncomingOrderRequest,
@ -247,6 +252,14 @@ fn to_cfd_state(
model::cfd::CfdState::SetupFailed { .. } => CfdState::SetupFailed, model::cfd::CfdState::SetupFailed { .. } => CfdState::SetupFailed,
model::cfd::CfdState::PendingCommit { .. } => CfdState::PendingCommit, model::cfd::CfdState::PendingCommit { .. } => CfdState::PendingCommit,
}, },
Some(UpdateCfdProposal::RollOverProposal {
direction: SettlementKind::Outgoing,
..
}) => CfdState::OutgoingRollOverProposal,
Some(UpdateCfdProposal::RollOverProposal {
direction: SettlementKind::Incoming,
..
}) => CfdState::IncomingRollOverProposal,
} }
} }

5
daemon/src/wire.rs

@ -33,6 +33,10 @@ pub enum TakerToMaker {
#[serde(with = "::bdk::bitcoin::util::amount::serde::as_btc")] #[serde(with = "::bdk::bitcoin::util::amount::serde::as_btc")]
maker: Amount, maker: Amount,
}, },
ProposeRollOver {
order_id: OrderId,
timestamp: SystemTime,
},
Protocol(SetupMsg), Protocol(SetupMsg),
} }
@ -42,6 +46,7 @@ impl fmt::Display for TakerToMaker {
TakerToMaker::TakeOrder { .. } => write!(f, "TakeOrder"), TakerToMaker::TakeOrder { .. } => write!(f, "TakeOrder"),
TakerToMaker::ProposeSettlement { .. } => write!(f, "ProposeSettlement"), TakerToMaker::ProposeSettlement { .. } => write!(f, "ProposeSettlement"),
TakerToMaker::Protocol(_) => write!(f, "Protocol"), TakerToMaker::Protocol(_) => write!(f, "Protocol"),
TakerToMaker::ProposeRollOver { .. } => write!(f, "ProposeRollOver"),
} }
} }
} }

7
frontend/src/MakerApp.tsx

@ -68,6 +68,9 @@ export default function App() {
const acceptOrRejectSettlement = cfds.filter((value) => const acceptOrRejectSettlement = cfds.filter((value) =>
value.state.getGroup() === StateGroupKey.ACCEPT_OR_REJECT_SETTLEMENT value.state.getGroup() === StateGroupKey.ACCEPT_OR_REJECT_SETTLEMENT
); );
const acceptOrRejectRollOvers = cfds.filter((value) =>
value.state.getGroup() === StateGroupKey.ACCEPT_OR_REJECT_ROLL_OVER
);
const opening = cfds.filter((value) => value.state.getGroup() === StateGroupKey.OPENING); const opening = cfds.filter((value) => value.state.getGroup() === StateGroupKey.OPENING);
const open = cfds.filter((value) => value.state.getGroup() === StateGroupKey.OPEN); const open = cfds.filter((value) => value.state.getGroup() === StateGroupKey.OPEN);
const closed = cfds.filter((value) => value.state.getGroup() === StateGroupKey.CLOSED); const closed = cfds.filter((value) => value.state.getGroup() === StateGroupKey.CLOSED);
@ -161,6 +164,7 @@ export default function App() {
<Tab>Open [{open.length}]</Tab> <Tab>Open [{open.length}]</Tab>
<Tab>Accept / Reject Order [{acceptOrRejectOrder.length}]</Tab> <Tab>Accept / Reject Order [{acceptOrRejectOrder.length}]</Tab>
<Tab>Accept / Reject Settlement [{acceptOrRejectSettlement.length}]</Tab> <Tab>Accept / Reject Settlement [{acceptOrRejectSettlement.length}]</Tab>
<Tab>Accept / Reject Roll Overs [{acceptOrRejectRollOvers.length}]</Tab>
<Tab>Opening [{opening.length}]</Tab> <Tab>Opening [{opening.length}]</Tab>
<Tab>Closed [{closed.length}]</Tab> <Tab>Closed [{closed.length}]</Tab>
</TabList> </TabList>
@ -175,6 +179,9 @@ export default function App() {
<TabPanel> <TabPanel>
<CfdTable data={acceptOrRejectSettlement} /> <CfdTable data={acceptOrRejectSettlement} />
</TabPanel> </TabPanel>
<TabPanel>
<CfdTable data={acceptOrRejectRollOvers} />
</TabPanel>
<TabPanel> <TabPanel>
<CfdTable data={opening} /> <CfdTable data={opening} />
</TabPanel> </TabPanel>

15
frontend/src/components/Types.tsx

@ -77,6 +77,10 @@ export class State {
return "Settlement Proposed"; return "Settlement Proposed";
case StateKey.OUTGOING_SETTLEMENT_PROPOSAL: case StateKey.OUTGOING_SETTLEMENT_PROPOSAL:
return "Settlement Proposed"; return "Settlement Proposed";
case StateKey.INCOMING_ROLL_OVER_PROPOSAL:
return "Rollover Proposed";
case StateKey.OUTGOING_ROLL_OVER_PROPOSAL:
return "Rollover Proposed";
case StateKey.MUST_REFUND: case StateKey.MUST_REFUND:
return "Refunding"; return "Refunding";
case StateKey.REFUNDED: case StateKey.REFUNDED:
@ -109,6 +113,8 @@ export class State {
case StateKey.INCOMING_ORDER_REQUEST: case StateKey.INCOMING_ORDER_REQUEST:
case StateKey.OUTGOING_SETTLEMENT_PROPOSAL: case StateKey.OUTGOING_SETTLEMENT_PROPOSAL:
case StateKey.INCOMING_SETTLEMENT_PROPOSAL: case StateKey.INCOMING_SETTLEMENT_PROPOSAL:
case StateKey.INCOMING_ROLL_OVER_PROPOSAL:
case StateKey.OUTGOING_ROLL_OVER_PROPOSAL:
case StateKey.CONTRACT_SETUP: case StateKey.CONTRACT_SETUP:
case StateKey.PENDING_OPEN: case StateKey.PENDING_OPEN:
case StateKey.REFUNDED: case StateKey.REFUNDED:
@ -133,11 +139,15 @@ export class State {
case StateKey.OPEN_COMMITTED: case StateKey.OPEN_COMMITTED:
case StateKey.MUST_REFUND: case StateKey.MUST_REFUND:
case StateKey.OUTGOING_SETTLEMENT_PROPOSAL: case StateKey.OUTGOING_SETTLEMENT_PROPOSAL:
case StateKey.OUTGOING_ROLL_OVER_PROPOSAL:
return StateGroupKey.OPEN; return StateGroupKey.OPEN;
case StateKey.INCOMING_SETTLEMENT_PROPOSAL: case StateKey.INCOMING_SETTLEMENT_PROPOSAL:
return StateGroupKey.ACCEPT_OR_REJECT_SETTLEMENT; return StateGroupKey.ACCEPT_OR_REJECT_SETTLEMENT;
case StateKey.INCOMING_ROLL_OVER_PROPOSAL:
return StateGroupKey.ACCEPT_OR_REJECT_ROLL_OVER;
case StateKey.REJECTED: case StateKey.REJECTED:
case StateKey.REFUNDED: case StateKey.REFUNDED:
case StateKey.SETUP_FAILED: case StateKey.SETUP_FAILED:
@ -153,6 +163,8 @@ export enum Action {
SETTLE = "settle", SETTLE = "settle",
ACCEPT_SETTLEMENT = "acceptSettlement", ACCEPT_SETTLEMENT = "acceptSettlement",
REJECT_SETTLEMENT = "rejectSettlement", REJECT_SETTLEMENT = "rejectSettlement",
ACCEPT_ROLL_OVER = "acceptRollOver",
REJECT_ROLL_OVER = "rejectRollOver",
} }
const enum StateKey { const enum StateKey {
@ -167,6 +179,8 @@ const enum StateKey {
OPEN_COMMITTED = "OpenCommitted", OPEN_COMMITTED = "OpenCommitted",
OUTGOING_SETTLEMENT_PROPOSAL = "OutgoingSettlementProposal", OUTGOING_SETTLEMENT_PROPOSAL = "OutgoingSettlementProposal",
INCOMING_SETTLEMENT_PROPOSAL = "IncomingSettlementProposal", INCOMING_SETTLEMENT_PROPOSAL = "IncomingSettlementProposal",
OUTGOING_ROLL_OVER_PROPOSAL = "OutgoingRollOverProposal",
INCOMING_ROLL_OVER_PROPOSAL = "IncomingRollOverProposal",
MUST_REFUND = "MustRefund", MUST_REFUND = "MustRefund",
REFUNDED = "Refunded", REFUNDED = "Refunded",
SETUP_FAILED = "SetupFailed", SETUP_FAILED = "SetupFailed",
@ -179,6 +193,7 @@ export enum StateGroupKey {
/// A CFD that is an ongoing open position (on chain) /// A CFD that is an ongoing open position (on chain)
OPEN = "Open", OPEN = "Open",
ACCEPT_OR_REJECT_SETTLEMENT = "Accept / Reject Settlement", ACCEPT_OR_REJECT_SETTLEMENT = "Accept / Reject Settlement",
ACCEPT_OR_REJECT_ROLL_OVER = "Accept / Reject Roll Over",
/// A CFD that has been successfully or not-successfully terminated /// A CFD that has been successfully or not-successfully terminated
CLOSED = "Closed", CLOSED = "Closed",
} }

4
frontend/src/components/cfdtables/CfdTable.tsx

@ -216,6 +216,10 @@ function colorSchemaForAction(action: Action): string {
return "green"; return "green";
case Action.REJECT_SETTLEMENT: case Action.REJECT_SETTLEMENT:
return "red"; return "red";
case Action.ACCEPT_ROLL_OVER:
return "green";
case Action.REJECT_ROLL_OVER:
return "red";
} }
} }

Loading…
Cancel
Save