|
|
@ -1464,6 +1464,181 @@ fn calculate_profit( |
|
|
|
Ok((profit, Percent(percent))) |
|
|
|
} |
|
|
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] |
|
|
|
pub struct Cet { |
|
|
|
pub tx: Transaction, |
|
|
|
pub adaptor_sig: EcdsaAdaptorSignature, |
|
|
|
|
|
|
|
// TODO: Range + number of digits (usize) could be represented as Digits similar to what we do
|
|
|
|
// in the protocol lib
|
|
|
|
pub range: RangeInclusive<u64>, |
|
|
|
pub n_bits: usize, |
|
|
|
} |
|
|
|
|
|
|
|
/// Contains all data we've assembled about the CFD through the setup protocol.
|
|
|
|
///
|
|
|
|
/// All contained signatures are the signatures of THE OTHER PARTY.
|
|
|
|
/// To use any of these transactions, we need to re-sign them with the correct secret key.
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] |
|
|
|
pub struct Dlc { |
|
|
|
pub identity: SecretKey, |
|
|
|
pub identity_counterparty: PublicKey, |
|
|
|
pub revocation: SecretKey, |
|
|
|
pub revocation_pk_counterparty: PublicKey, |
|
|
|
pub publish: SecretKey, |
|
|
|
pub publish_pk_counterparty: PublicKey, |
|
|
|
pub maker_address: Address, |
|
|
|
pub taker_address: Address, |
|
|
|
|
|
|
|
/// The fully signed lock transaction ready to be published on chain
|
|
|
|
pub lock: (Transaction, Descriptor<PublicKey>), |
|
|
|
pub commit: (Transaction, EcdsaAdaptorSignature, Descriptor<PublicKey>), |
|
|
|
pub cets: HashMap<BitMexPriceEventId, Vec<Cet>>, |
|
|
|
pub refund: (Transaction, Signature), |
|
|
|
|
|
|
|
#[serde(with = "::bdk::bitcoin::util::amount::serde::as_sat")] |
|
|
|
pub maker_lock_amount: Amount, |
|
|
|
#[serde(with = "::bdk::bitcoin::util::amount::serde::as_sat")] |
|
|
|
pub taker_lock_amount: Amount, |
|
|
|
|
|
|
|
pub revoked_commit: Vec<RevokedCommit>, |
|
|
|
} |
|
|
|
|
|
|
|
impl Dlc { |
|
|
|
/// Create a close transaction based on the current contract and a settlement proposals
|
|
|
|
pub fn close_transaction( |
|
|
|
&self, |
|
|
|
proposal: &crate::model::cfd::SettlementProposal, |
|
|
|
) -> Result<(Transaction, Signature)> { |
|
|
|
let (lock_tx, lock_desc) = &self.lock; |
|
|
|
let (lock_outpoint, lock_amount) = { |
|
|
|
let outpoint = lock_tx |
|
|
|
.outpoint(&lock_desc.script_pubkey()) |
|
|
|
.expect("lock script to be in lock tx"); |
|
|
|
let amount = Amount::from_sat(lock_tx.output[outpoint.vout as usize].value); |
|
|
|
|
|
|
|
(outpoint, amount) |
|
|
|
}; |
|
|
|
let (tx, sighash) = maia::close_transaction( |
|
|
|
lock_desc, |
|
|
|
lock_outpoint, |
|
|
|
lock_amount, |
|
|
|
(&self.maker_address, proposal.maker), |
|
|
|
(&self.taker_address, proposal.taker), |
|
|
|
) |
|
|
|
.context("Unable to collaborative close transaction")?; |
|
|
|
|
|
|
|
let sig = SECP256K1.sign(&sighash, &self.identity); |
|
|
|
|
|
|
|
Ok((tx, sig)) |
|
|
|
} |
|
|
|
|
|
|
|
pub fn finalize_spend_transaction( |
|
|
|
&self, |
|
|
|
(close_tx, own_sig): (Transaction, Signature), |
|
|
|
counterparty_sig: Signature, |
|
|
|
) -> Result<Transaction> { |
|
|
|
let own_pk = PublicKey::new(secp256k1_zkp::PublicKey::from_secret_key( |
|
|
|
SECP256K1, |
|
|
|
&self.identity, |
|
|
|
)); |
|
|
|
|
|
|
|
let (_, lock_desc) = &self.lock; |
|
|
|
let spend_tx = maia::finalize_spend_transaction( |
|
|
|
close_tx, |
|
|
|
lock_desc, |
|
|
|
(own_pk, own_sig), |
|
|
|
(self.identity_counterparty, counterparty_sig), |
|
|
|
)?; |
|
|
|
|
|
|
|
Ok(spend_tx) |
|
|
|
} |
|
|
|
|
|
|
|
pub fn refund_amount(&self, role: Role) -> Amount { |
|
|
|
let our_script_pubkey = match role { |
|
|
|
Role::Taker => self.taker_address.script_pubkey(), |
|
|
|
Role::Maker => self.maker_address.script_pubkey(), |
|
|
|
}; |
|
|
|
|
|
|
|
self.refund |
|
|
|
.0 |
|
|
|
.output |
|
|
|
.iter() |
|
|
|
.find(|output| output.script_pubkey == our_script_pubkey) |
|
|
|
.map(|output| Amount::from_sat(output.value)) |
|
|
|
.unwrap_or_default() |
|
|
|
} |
|
|
|
|
|
|
|
pub fn script_pubkey_for(&self, role: Role) -> Script { |
|
|
|
match role { |
|
|
|
Role::Maker => self.maker_address.script_pubkey(), |
|
|
|
Role::Taker => self.taker_address.script_pubkey(), |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// Information which we need to remember in order to construct a
|
|
|
|
/// punishment transaction in case the counterparty publishes a
|
|
|
|
/// revoked commit transaction.
|
|
|
|
///
|
|
|
|
/// It also includes the information needed to monitor for the
|
|
|
|
/// publication of the revoked commit transaction.
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] |
|
|
|
pub struct RevokedCommit { |
|
|
|
// To build punish transaction
|
|
|
|
pub encsig_ours: EcdsaAdaptorSignature, |
|
|
|
pub revocation_sk_theirs: SecretKey, |
|
|
|
pub publication_pk_theirs: PublicKey, |
|
|
|
// To monitor revoked commit transaction
|
|
|
|
pub txid: Txid, |
|
|
|
pub script_pubkey: Script, |
|
|
|
} |
|
|
|
|
|
|
|
/// Used when transactions (e.g. collaborative close) are recorded as a part of
|
|
|
|
/// CfdState in the cases when we can't solely rely on state transition
|
|
|
|
/// timestamp as it could have occured for different reasons (like a new
|
|
|
|
/// attestation in Open state)
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] |
|
|
|
pub struct CollaborativeSettlement { |
|
|
|
pub tx: Transaction, |
|
|
|
pub timestamp: Timestamp, |
|
|
|
#[serde(with = "::bdk::bitcoin::util::amount::serde::as_sat")] |
|
|
|
payout: Amount, |
|
|
|
price: Price, |
|
|
|
} |
|
|
|
|
|
|
|
impl CollaborativeSettlement { |
|
|
|
pub fn new(tx: Transaction, own_script_pubkey: Script, price: Price) -> Result<Self> { |
|
|
|
// Falls back to Amount::ZERO in case we don't find an output that matches out script pubkey
|
|
|
|
// The assumption is, that this can happen for cases where we were liuqidated
|
|
|
|
let payout = match tx |
|
|
|
.output |
|
|
|
.iter() |
|
|
|
.find(|output| output.script_pubkey == own_script_pubkey) |
|
|
|
.map(|output| Amount::from_sat(output.value)) |
|
|
|
{ |
|
|
|
Some(payout) => payout, |
|
|
|
None => { |
|
|
|
tracing::error!( |
|
|
|
"Collaborative settlement with a zero amount, this should really not happen!" |
|
|
|
); |
|
|
|
Amount::ZERO |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
Ok(Self { |
|
|
|
tx, |
|
|
|
timestamp: Timestamp::now(), |
|
|
|
payout, |
|
|
|
price, |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
pub fn payout(&self) -> Amount { |
|
|
|
self.payout |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
#[cfg(test)] |
|
|
|
mod tests { |
|
|
|
use super::*; |
|
|
@ -1715,178 +1890,3 @@ mod tests { |
|
|
|
assert_eq!(id, deserialized); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] |
|
|
|
pub struct Cet { |
|
|
|
pub tx: Transaction, |
|
|
|
pub adaptor_sig: EcdsaAdaptorSignature, |
|
|
|
|
|
|
|
// TODO: Range + number of digits (usize) could be represented as Digits similar to what we do
|
|
|
|
// in the protocol lib
|
|
|
|
pub range: RangeInclusive<u64>, |
|
|
|
pub n_bits: usize, |
|
|
|
} |
|
|
|
|
|
|
|
/// Contains all data we've assembled about the CFD through the setup protocol.
|
|
|
|
///
|
|
|
|
/// All contained signatures are the signatures of THE OTHER PARTY.
|
|
|
|
/// To use any of these transactions, we need to re-sign them with the correct secret key.
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] |
|
|
|
pub struct Dlc { |
|
|
|
pub identity: SecretKey, |
|
|
|
pub identity_counterparty: PublicKey, |
|
|
|
pub revocation: SecretKey, |
|
|
|
pub revocation_pk_counterparty: PublicKey, |
|
|
|
pub publish: SecretKey, |
|
|
|
pub publish_pk_counterparty: PublicKey, |
|
|
|
pub maker_address: Address, |
|
|
|
pub taker_address: Address, |
|
|
|
|
|
|
|
/// The fully signed lock transaction ready to be published on chain
|
|
|
|
pub lock: (Transaction, Descriptor<PublicKey>), |
|
|
|
pub commit: (Transaction, EcdsaAdaptorSignature, Descriptor<PublicKey>), |
|
|
|
pub cets: HashMap<BitMexPriceEventId, Vec<Cet>>, |
|
|
|
pub refund: (Transaction, Signature), |
|
|
|
|
|
|
|
#[serde(with = "::bdk::bitcoin::util::amount::serde::as_sat")] |
|
|
|
pub maker_lock_amount: Amount, |
|
|
|
#[serde(with = "::bdk::bitcoin::util::amount::serde::as_sat")] |
|
|
|
pub taker_lock_amount: Amount, |
|
|
|
|
|
|
|
pub revoked_commit: Vec<RevokedCommit>, |
|
|
|
} |
|
|
|
|
|
|
|
impl Dlc { |
|
|
|
/// Create a close transaction based on the current contract and a settlement proposals
|
|
|
|
pub fn close_transaction( |
|
|
|
&self, |
|
|
|
proposal: &crate::model::cfd::SettlementProposal, |
|
|
|
) -> Result<(Transaction, Signature)> { |
|
|
|
let (lock_tx, lock_desc) = &self.lock; |
|
|
|
let (lock_outpoint, lock_amount) = { |
|
|
|
let outpoint = lock_tx |
|
|
|
.outpoint(&lock_desc.script_pubkey()) |
|
|
|
.expect("lock script to be in lock tx"); |
|
|
|
let amount = Amount::from_sat(lock_tx.output[outpoint.vout as usize].value); |
|
|
|
|
|
|
|
(outpoint, amount) |
|
|
|
}; |
|
|
|
let (tx, sighash) = maia::close_transaction( |
|
|
|
lock_desc, |
|
|
|
lock_outpoint, |
|
|
|
lock_amount, |
|
|
|
(&self.maker_address, proposal.maker), |
|
|
|
(&self.taker_address, proposal.taker), |
|
|
|
) |
|
|
|
.context("Unable to collaborative close transaction")?; |
|
|
|
|
|
|
|
let sig = SECP256K1.sign(&sighash, &self.identity); |
|
|
|
|
|
|
|
Ok((tx, sig)) |
|
|
|
} |
|
|
|
|
|
|
|
pub fn finalize_spend_transaction( |
|
|
|
&self, |
|
|
|
(close_tx, own_sig): (Transaction, Signature), |
|
|
|
counterparty_sig: Signature, |
|
|
|
) -> Result<Transaction> { |
|
|
|
let own_pk = PublicKey::new(secp256k1_zkp::PublicKey::from_secret_key( |
|
|
|
SECP256K1, |
|
|
|
&self.identity, |
|
|
|
)); |
|
|
|
|
|
|
|
let (_, lock_desc) = &self.lock; |
|
|
|
let spend_tx = maia::finalize_spend_transaction( |
|
|
|
close_tx, |
|
|
|
lock_desc, |
|
|
|
(own_pk, own_sig), |
|
|
|
(self.identity_counterparty, counterparty_sig), |
|
|
|
)?; |
|
|
|
|
|
|
|
Ok(spend_tx) |
|
|
|
} |
|
|
|
|
|
|
|
pub fn refund_amount(&self, role: Role) -> Amount { |
|
|
|
let our_script_pubkey = match role { |
|
|
|
Role::Taker => self.taker_address.script_pubkey(), |
|
|
|
Role::Maker => self.maker_address.script_pubkey(), |
|
|
|
}; |
|
|
|
|
|
|
|
self.refund |
|
|
|
.0 |
|
|
|
.output |
|
|
|
.iter() |
|
|
|
.find(|output| output.script_pubkey == our_script_pubkey) |
|
|
|
.map(|output| Amount::from_sat(output.value)) |
|
|
|
.unwrap_or_default() |
|
|
|
} |
|
|
|
|
|
|
|
pub fn script_pubkey_for(&self, role: Role) -> Script { |
|
|
|
match role { |
|
|
|
Role::Maker => self.maker_address.script_pubkey(), |
|
|
|
Role::Taker => self.taker_address.script_pubkey(), |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// Information which we need to remember in order to construct a
|
|
|
|
/// punishment transaction in case the counterparty publishes a
|
|
|
|
/// revoked commit transaction.
|
|
|
|
///
|
|
|
|
/// It also includes the information needed to monitor for the
|
|
|
|
/// publication of the revoked commit transaction.
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] |
|
|
|
pub struct RevokedCommit { |
|
|
|
// To build punish transaction
|
|
|
|
pub encsig_ours: EcdsaAdaptorSignature, |
|
|
|
pub revocation_sk_theirs: SecretKey, |
|
|
|
pub publication_pk_theirs: PublicKey, |
|
|
|
// To monitor revoked commit transaction
|
|
|
|
pub txid: Txid, |
|
|
|
pub script_pubkey: Script, |
|
|
|
} |
|
|
|
|
|
|
|
/// Used when transactions (e.g. collaborative close) are recorded as a part of
|
|
|
|
/// CfdState in the cases when we can't solely rely on state transition
|
|
|
|
/// timestamp as it could have occured for different reasons (like a new
|
|
|
|
/// attestation in Open state)
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] |
|
|
|
pub struct CollaborativeSettlement { |
|
|
|
pub tx: Transaction, |
|
|
|
pub timestamp: Timestamp, |
|
|
|
#[serde(with = "::bdk::bitcoin::util::amount::serde::as_sat")] |
|
|
|
payout: Amount, |
|
|
|
price: Price, |
|
|
|
} |
|
|
|
|
|
|
|
impl CollaborativeSettlement { |
|
|
|
pub fn new(tx: Transaction, own_script_pubkey: Script, price: Price) -> Result<Self> { |
|
|
|
// Falls back to Amount::ZERO in case we don't find an output that matches out script pubkey
|
|
|
|
// The assumption is, that this can happen for cases where we were liuqidated
|
|
|
|
let payout = match tx |
|
|
|
.output |
|
|
|
.iter() |
|
|
|
.find(|output| output.script_pubkey == own_script_pubkey) |
|
|
|
.map(|output| Amount::from_sat(output.value)) |
|
|
|
{ |
|
|
|
Some(payout) => payout, |
|
|
|
None => { |
|
|
|
tracing::error!( |
|
|
|
"Collaborative settlement with a zero amount, this should really not happen!" |
|
|
|
); |
|
|
|
Amount::ZERO |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
Ok(Self { |
|
|
|
tx, |
|
|
|
timestamp: Timestamp::now(), |
|
|
|
payout, |
|
|
|
price, |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
pub fn payout(&self) -> Amount { |
|
|
|
self.payout |
|
|
|
} |
|
|
|
} |
|
|
|