Browse Source

Make payout and attestation price available in `model::Attestation`

Profit based on attested price as soon as we have an `Attestation`.
refactor/no-log-handler
Daniel Karzel 3 years ago
parent
commit
bf596dfdad
No known key found for this signature in database GPG Key ID: 30C3FC2E438ADB6E
  1. 16
      daemon/src/maker_cfd.rs
  2. 160
      daemon/src/model/cfd.rs
  3. 11
      daemon/src/monitor.rs
  4. 16
      daemon/src/taker_cfd.rs

16
daemon/src/maker_cfd.rs

@ -5,8 +5,9 @@ use crate::db::{
};
use crate::maker_inc_connections::TakerCommand;
use crate::model::cfd::{
Cfd, CfdState, CfdStateChangeEvent, CfdStateCommon, Dlc, Order, OrderId, Origin, Role,
RollOverProposal, SettlementKind, SettlementProposal, UpdateCfdProposal, UpdateCfdProposals,
Attestation, Cfd, CfdState, CfdStateChangeEvent, CfdStateCommon, Dlc, Order, OrderId, Origin,
Role, RollOverProposal, SettlementKind, SettlementProposal, UpdateCfdProposal,
UpdateCfdProposals,
};
use crate::model::{TakerId, Usd};
use crate::monitor::MonitorParams;
@ -891,9 +892,14 @@ impl Actor {
for mut cfd in cfds {
if cfd
.handle(CfdStateChangeEvent::OracleAttestation(
attestation.clone().into(),
))?
.handle(CfdStateChangeEvent::OracleAttestation(Attestation::new(
attestation.id.clone(),
attestation.price,
attestation.scalars.clone(),
cfd.dlc()
.context("No DLC available when attestation was received")?,
cfd.role(),
)?))?
.is_none()
{
// if we don't transition to a new state after oracle attestation we ignore the cfd

160
daemon/src/model/cfd.rs

@ -248,7 +248,7 @@ pub enum CfdState {
PendingCet {
common: CfdStateCommon,
dlc: Dlc,
cet_status: CetStatus,
attestation: Attestation,
},
/// The position was closed collaboratively or non-collaboratively
@ -258,7 +258,10 @@ pub enum CfdState {
/// This is the final state for all happy-path scenarios where we had an open position and then
/// "settled" it. Settlement can be collaboratively or non-collaboratively (by publishing
/// commit + cet).
Closed { common: CfdStateCommon },
Closed {
common: CfdStateCommon,
attestation: Attestation,
},
// TODO: Can be extended with CetStatus
/// The CFD contract's refund transaction was published but it not final yet
@ -284,17 +287,50 @@ pub enum CfdState {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Attestation {
pub id: OracleEventId,
pub price: u64,
pub scalars: Vec<SecretKey>,
#[serde(with = "::bdk::bitcoin::util::amount::serde::as_sat")]
payout: Amount,
price: u64,
}
impl From<oracle::Attestation> for Attestation {
fn from(attestation: oracle::Attestation) -> Self {
Attestation {
id: attestation.id,
price: attestation.price,
scalars: attestation.scalars,
impl Attestation {
pub fn new(
id: OracleEventId,
price: u64,
scalars: Vec<SecretKey>,
dlc: Dlc,
role: Role,
) -> Result<Self> {
let cet = dlc
.cets
.iter()
.find_map(|(_, cet)| cet.iter().find(|cet| cet.range.contains(&price)))
.context("Unable to find attested price in any range")?;
let payout = cet
.tx
.output
.iter()
.find_map(|output| {
(output.script_pubkey
== match role {
Role::Maker => dlc.maker_address.script_pubkey(),
Role::Taker => dlc.taker_address.script_pubkey(),
})
.then(|| Amount::from_sat(output.value))
})
.unwrap_or_default();
Ok(Self {
id,
price,
scalars,
payout,
})
}
pub fn price(&self) -> Usd {
Usd(Decimal::from(self.price))
}
}
@ -483,6 +519,14 @@ impl Cfd {
}
pub fn profit(&self, current_price: Usd) -> Result<(SignedAmount, Percent)> {
// TODO: We should use the payout curve here and not just the current price!
let current_price = if let Some(attestation) = self.attestation() {
attestation.price()
} else {
current_price
};
let (p_n_l, p_n_l_percent) = calculate_profit(
self.order.price,
current_price,
@ -708,11 +752,18 @@ impl Cfd {
},
}
}
monitor::Event::CetFinality(_) => Closed {
monitor::Event::CetFinality(_) => {
let attestation = self
.attestation()
.context("No attestation available when reaching CET finality")?;
CfdState::Closed {
common: CfdStateCommon {
transition_timestamp: SystemTime::now(),
},
},
attestation,
}
}
monitor::Event::RevokedTransactionFound(_) => {
todo!("Punish bad counterparty")
}
@ -790,21 +841,20 @@ impl Cfd {
self.state
),
},
CfdStateChangeEvent::CetSent => match self.state.clone() {
CfdState::OpenCommitted {
common,
dlc,
cet_status,
} => CfdState::PendingCet {
common,
dlc,
cet_status,
},
_ => bail!(
"Cannot transition to PendingCet because of unexpected state {}",
self.state
),
CfdStateChangeEvent::CetSent => {
let dlc = self.dlc().context("No DLC available after CET was sent")?;
let attestation = self
.attestation()
.context("No attestation available after CET was sent")?;
CfdState::PendingCet {
common: CfdStateCommon {
transition_timestamp: SystemTime::now(),
},
dlc,
attestation,
}
}
};
self.state = new_state.clone();
@ -886,9 +936,7 @@ impl Cfd {
..
}
| CfdState::PendingCet {
dlc,
cet_status: CetStatus::Ready(attestation),
..
dlc, attestation, ..
} => (dlc, attestation),
CfdState::OpenCommitted { cet_status, .. } => {
return Ok(Err(NotReadyYet { cet_status }));
@ -988,6 +1036,62 @@ impl Cfd {
pub fn role(&self) -> Role {
self.order.origin.into()
}
pub fn dlc(&self) -> Option<Dlc> {
match self.state.clone() {
CfdState::PendingOpen { dlc, .. }
| CfdState::Open { dlc, .. }
| CfdState::PendingCommit { dlc, .. }
| CfdState::OpenCommitted { dlc, .. }
| CfdState::PendingCet { dlc, .. } => Some(dlc),
CfdState::OutgoingOrderRequest { .. }
| CfdState::IncomingOrderRequest { .. }
| CfdState::Accepted { .. }
| CfdState::Rejected { .. }
| CfdState::ContractSetup { .. }
| CfdState::Closed { .. }
| CfdState::MustRefund { .. }
| CfdState::Refunded { .. }
| CfdState::SetupFailed { .. } => None,
}
}
fn attestation(&self) -> Option<Attestation> {
match self.state.clone() {
CfdState::PendingOpen {
attestation: Some(attestation),
..
}
| CfdState::Open {
attestation: Some(attestation),
..
}
| CfdState::PendingCommit {
attestation: Some(attestation),
..
}
| CfdState::OpenCommitted {
cet_status: CetStatus::OracleSigned(attestation) | CetStatus::Ready(attestation),
..
}
| CfdState::PendingCet { attestation, .. }
| CfdState::Closed { attestation, .. } => Some(attestation),
CfdState::OutgoingOrderRequest { .. }
| CfdState::IncomingOrderRequest { .. }
| CfdState::Accepted { .. }
| CfdState::Rejected { .. }
| CfdState::ContractSetup { .. }
| CfdState::PendingOpen { .. }
| CfdState::Open { .. }
| CfdState::PendingCommit { .. }
| CfdState::OpenCommitted { .. }
| CfdState::MustRefund { .. }
| CfdState::Refunded { .. }
| CfdState::SetupFailed { .. } => None,
}
}
}
#[derive(thiserror::Error, Debug, Clone)]

11
daemon/src/monitor.rs

@ -91,8 +91,7 @@ where
actor.monitor_commit_refund_timelock(&params, cfd.order.id);
actor.monitor_refund_finality(&params,cfd.order.id);
}
CfdState::OpenCommitted { dlc, cet_status, .. }
| CfdState::PendingCet { dlc, cet_status, .. } => {
CfdState::OpenCommitted { dlc, cet_status, .. } => {
let params = MonitorParams::from_dlc_and_timelocks(dlc.clone(), cfd.refund_timelock_in_blocks());
actor.cfds.insert(cfd.order.id, params.clone());
@ -119,6 +118,14 @@ where
}
}
}
CfdState::PendingCet { dlc, attestation, .. } => {
let params = MonitorParams::from_dlc_and_timelocks(dlc.clone(), cfd.refund_timelock_in_blocks());
actor.cfds.insert(cfd.order.id, params.clone());
actor.monitor_cet_finality(map_cets(dlc.cets), attestation.into(), cfd.order.id)?;
actor.monitor_commit_refund_timelock(&params, cfd.order.id);
actor.monitor_refund_finality(&params,cfd.order.id);
}
CfdState::MustRefund { dlc, .. } => {
let params = MonitorParams::from_dlc_and_timelocks(dlc.clone(), cfd.refund_timelock_in_blocks());
actor.cfds.insert(cfd.order.id, params.clone());

16
daemon/src/taker_cfd.rs

@ -4,8 +4,9 @@ use crate::db::{
load_cfd_by_order_id, load_cfds_by_oracle_event_id, load_order_by_id,
};
use crate::model::cfd::{
Cfd, CfdState, CfdStateChangeEvent, CfdStateCommon, Dlc, Order, OrderId, Origin, Role,
RollOverProposal, SettlementKind, SettlementProposal, UpdateCfdProposal, UpdateCfdProposals,
Attestation, Cfd, CfdState, CfdStateChangeEvent, CfdStateCommon, Dlc, Order, OrderId, Origin,
Role, RollOverProposal, SettlementKind, SettlementProposal, UpdateCfdProposal,
UpdateCfdProposals,
};
use crate::model::{OracleEventId, Usd};
use crate::monitor::{self, MonitorParams};
@ -632,9 +633,14 @@ impl Actor {
for mut cfd in cfds {
if cfd
.handle(CfdStateChangeEvent::OracleAttestation(
attestation.clone().into(),
))?
.handle(CfdStateChangeEvent::OracleAttestation(Attestation::new(
attestation.id.clone(),
attestation.price,
attestation.scalars.clone(),
cfd.dlc()
.context("No DLC available when attestation was received")?,
cfd.role(),
)?))?
.is_none()
{
// if we don't transition to a new state after oracle attestation we ignore the cfd

Loading…
Cancel
Save