mirror of
https://github.com/conduition/dlctix.git
synced 2026-01-30 21:25:10 +01:00
reusable shared fee computation
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
use bitcoin::{transaction::InputWeightPrediction, Amount, FeeRate};
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::errors::Error;
|
||||
|
||||
/// Compute the fee for a transaction given a fixed [`FeeRate`], input weights,
|
||||
@@ -35,3 +37,38 @@ pub(crate) fn fee_subtract_safe(
|
||||
}
|
||||
Ok(after_fee)
|
||||
}
|
||||
|
||||
/// Safely compute the output amounts for a set of outputs by computing
|
||||
/// and distributing the fee equally among all output values.
|
||||
///
|
||||
/// Returns an error if any output value is negative, or is less than the
|
||||
/// given dust threshold.
|
||||
pub(crate) fn fee_calc_shared<'k, I, O, T>(
|
||||
available_coins: Amount,
|
||||
fee_rate: FeeRate,
|
||||
input_weights: I,
|
||||
output_spk_lens: O,
|
||||
dust_threshold: Amount,
|
||||
payout_map: &'k BTreeMap<T, u64>,
|
||||
) -> Result<BTreeMap<&'k T, Amount>, Error>
|
||||
where
|
||||
I: IntoIterator<Item = InputWeightPrediction>,
|
||||
O: IntoIterator<Item = usize>,
|
||||
T: Clone + Ord,
|
||||
{
|
||||
let fee_total = fee_calc_safe(fee_rate, input_weights, output_spk_lens)?;
|
||||
|
||||
// Mining fees are distributed equally among all winners, regardless of payout weight.
|
||||
let fee_shared = fee_total / payout_map.len() as u64;
|
||||
let total_weight: u64 = payout_map.values().copied().sum();
|
||||
|
||||
// Payout amounts are computed by using relative weights.
|
||||
payout_map
|
||||
.iter()
|
||||
.map(|(key, &weight)| {
|
||||
let payout = available_coins * weight / total_weight;
|
||||
let payout_value = fee_subtract_safe(payout, fee_shared, dust_threshold)?;
|
||||
Ok((key, payout_value))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ pub(crate) mod fees;
|
||||
pub(crate) mod outcome;
|
||||
pub(crate) mod split;
|
||||
|
||||
use bitcoin::{Amount, FeeRate};
|
||||
use bitcoin::{transaction::InputWeightPrediction, Amount, FeeRate};
|
||||
use secp::Point;
|
||||
|
||||
use crate::{
|
||||
@@ -22,6 +22,9 @@ use std::collections::{BTreeMap, BTreeSet};
|
||||
/// ```not_rust
|
||||
/// total_payout = contract_value * weights[player] / sum(weights)
|
||||
/// ```
|
||||
///
|
||||
/// Players who should not receive a payout from an outcome MUST NOT be given an entry
|
||||
/// in a `PayoutWeights` map.
|
||||
pub type PayoutWeights = BTreeMap<Player, u64>;
|
||||
|
||||
/// Represents the parameters which all players and the market maker must agree on
|
||||
@@ -80,7 +83,7 @@ pub struct WinCondition {
|
||||
|
||||
impl ContractParameters {
|
||||
pub(crate) fn outcome_output_value(&self) -> Result<Amount, Error> {
|
||||
let input_weights = [bitcoin::transaction::InputWeightPrediction::P2TR_KEY_DEFAULT_SIGHASH];
|
||||
let input_weights = [InputWeightPrediction::P2TR_KEY_DEFAULT_SIGHASH];
|
||||
let fee = fees::fee_calc_safe(self.fee_rate, input_weights, [P2TR_SCRIPT_PUBKEY_SIZE])?;
|
||||
let outcome_value = fees::fee_subtract_safe(self.funding_value, fee, P2TR_DUST_VALUE)?;
|
||||
Ok(outcome_value)
|
||||
|
||||
@@ -13,17 +13,29 @@ use crate::{
|
||||
/// This contains cached data used for constructing further transactions,
|
||||
/// or signing the outcome transactions themselves.
|
||||
pub(crate) struct OutcomeTransactionBuildOutput {
|
||||
pub(crate) outcome_txs: Vec<Transaction>,
|
||||
pub(crate) outcome_spend_infos: Vec<OutcomeSpendInfo>,
|
||||
outcome_txs: Vec<Transaction>,
|
||||
outcome_spend_infos: Vec<OutcomeSpendInfo>,
|
||||
funding_spend_info: FundingSpendInfo,
|
||||
}
|
||||
|
||||
impl OutcomeTransactionBuildOutput {
|
||||
/// Return the set of mutually exclusive outcome transactions. One of these
|
||||
/// transactions will be executed depending on the oracle's attestation.
|
||||
pub fn outcome_txs(&self) -> &[Transaction] {
|
||||
pub(crate) fn outcome_txs(&self) -> &[Transaction] {
|
||||
&self.outcome_txs
|
||||
}
|
||||
|
||||
/// Return the set of mutually exclusive outcome spend info objects.
|
||||
pub(crate) fn outcome_spend_infos(&self) -> &[OutcomeSpendInfo] {
|
||||
&self.outcome_spend_infos
|
||||
}
|
||||
|
||||
/// Returns the number of [`musig2`] partial signatures required by each player
|
||||
/// in the DLC (and the market maker). This is the same as the length of
|
||||
/// [`Self::outcome_txs`].
|
||||
pub(crate) fn signatures_required(&self) -> usize {
|
||||
self.outcome_txs.len()
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a set of unsigned outcome transactions which spend from the funding TX.
|
||||
@@ -84,7 +96,7 @@ pub(crate) fn build_outcome_txs(
|
||||
/// Construct a set of partial signatures for the outcome transactions.
|
||||
///
|
||||
/// The number of signatures and nonces required can be computed by using
|
||||
/// checking the length of [`OutcomeTransactionBuildOutput::outcome_txs`].
|
||||
/// checking the length of [`OutcomeTransactionBuildOutput::signatures_required`].
|
||||
pub(crate) fn partial_sign_outcome_txs<'a>(
|
||||
params: &ContractParameters,
|
||||
outcome_build_out: &OutcomeTransactionBuildOutput,
|
||||
@@ -139,7 +151,7 @@ pub(crate) fn partial_sign_outcome_txs<'a>(
|
||||
/// Verify a player's partial adaptor signatures on the outcome transactions.
|
||||
///
|
||||
/// The number of signatures and nonces required can be computed by using
|
||||
/// checking the length of [`OutcomeTransactionBuildOutput::outcome_txs`].
|
||||
/// checking the length of [`OutcomeTransactionBuildOutput::signatures_required`].
|
||||
pub(crate) fn verify_outcome_tx_partial_signatures<'p, 'a>(
|
||||
params: &ContractParameters,
|
||||
outcome_build_out: &OutcomeTransactionBuildOutput,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use bitcoin::{absolute::LockTime, OutPoint, Sequence, Transaction, TxIn, TxOut};
|
||||
use bitcoin::{absolute::LockTime, Amount, OutPoint, Sequence, Transaction, TxIn, TxOut};
|
||||
use musig2::{AggNonce, CompactSignature, PartialSignature, PubNonce, SecNonce};
|
||||
use secp::Scalar;
|
||||
|
||||
@@ -24,9 +24,17 @@ pub(crate) struct SplitTransactionBuildOutput {
|
||||
impl SplitTransactionBuildOutput {
|
||||
/// Return the set of mutually exclusive split transactions. Each of these
|
||||
/// transactions spend from a corresponding previous outcome transaction.
|
||||
pub fn split_txs(&self) -> &[Transaction] {
|
||||
pub(crate) fn split_txs(&self) -> &[Transaction] {
|
||||
&self.split_txs
|
||||
}
|
||||
|
||||
/// Returns the number of [`musig2`] partial signatures required by each player
|
||||
/// in the DLC (and the market maker). This is the sum of all possible win conditions
|
||||
/// across every split transaction (i.e. counting the number of winners in every
|
||||
/// possible outcome).
|
||||
pub(crate) fn signatures_required(&self) -> usize {
|
||||
self.split_spend_infos.len()
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the set of split transactions which splits an outcome TX into per-player
|
||||
@@ -43,18 +51,21 @@ pub(crate) fn build_split_txs(
|
||||
let payout_map = params.outcome_payouts.get(outcome_index).ok_or(Error)?;
|
||||
|
||||
let outcome_spend_info = &outcome_build_output
|
||||
.outcome_spend_infos
|
||||
.outcome_spend_infos()
|
||||
.get(outcome_index)
|
||||
.ok_or(Error)?;
|
||||
|
||||
// Fee estimation
|
||||
let input_weight = outcome_spend_info.input_weight_for_split_tx();
|
||||
let spk_lengths = std::iter::repeat(P2TR_SCRIPT_PUBKEY_SIZE).take(payout_map.len());
|
||||
let fee_total = fees::fee_calc_safe(params.fee_rate, [input_weight], spk_lengths)?;
|
||||
|
||||
// Mining fees are distributed equally among all winners, regardless of payout weight.
|
||||
let fee_shared = fee_total / payout_map.len() as u64;
|
||||
let total_payout_weight: u64 = payout_map.values().copied().sum();
|
||||
let payout_values: BTreeMap<&Player, Amount> = fees::fee_calc_shared(
|
||||
outcome_spend_info.outcome_value(),
|
||||
params.fee_rate,
|
||||
[input_weight],
|
||||
spk_lengths,
|
||||
P2TR_DUST_VALUE,
|
||||
&payout_map,
|
||||
)?;
|
||||
|
||||
let (outcome_input, _) = contract::outcome::outcome_tx_prevout(
|
||||
outcome_build_output,
|
||||
@@ -62,13 +73,9 @@ pub(crate) fn build_split_txs(
|
||||
params.relative_locktime_block_delta, // Split TXs have 1*delta block delay
|
||||
)?;
|
||||
|
||||
// payout_map is a btree, so outputs are automatically sorted by player.
|
||||
// payout_values is a btree, so outputs are automatically sorted by player.
|
||||
let mut split_tx_outputs = Vec::with_capacity(payout_map.len());
|
||||
for (&player, &payout_weight) in payout_map.iter() {
|
||||
// Payout amounts are computed by using relative weights.
|
||||
let payout = outcome_spend_info.outcome_value() * payout_weight / total_payout_weight;
|
||||
let payout_value = fees::fee_subtract_safe(payout, fee_shared, P2TR_DUST_VALUE)?;
|
||||
|
||||
for (&&player, &payout_value) in payout_values.iter() {
|
||||
let split_spend_info = SplitSpendInfo::new(
|
||||
player,
|
||||
¶ms.market_maker,
|
||||
@@ -145,7 +152,7 @@ pub(crate) fn partial_sign_split_txs<'a>(
|
||||
|
||||
// Hash the split TX.
|
||||
let outcome_spend_info = outcome_build_out
|
||||
.outcome_spend_infos
|
||||
.outcome_spend_infos()
|
||||
.get(win_cond.outcome_index)
|
||||
.ok_or(Error)?;
|
||||
|
||||
@@ -197,7 +204,7 @@ pub(crate) fn verify_split_tx_partial_signatures(
|
||||
let partial_sig = partial_signatures.get(&win_cond).copied().ok_or(Error)?; // must provide all sigs
|
||||
|
||||
let outcome_spend_info = outcome_build_out
|
||||
.outcome_spend_infos
|
||||
.outcome_spend_infos()
|
||||
.get(win_cond.outcome_index)
|
||||
.ok_or(Error)?;
|
||||
|
||||
@@ -251,7 +258,7 @@ where
|
||||
let aggnonce = aggnonces.get(&win_cond).ok_or(Error)?;
|
||||
|
||||
let outcome_spend_info = outcome_build_out
|
||||
.outcome_spend_infos
|
||||
.outcome_spend_infos()
|
||||
.get(win_cond.outcome_index)
|
||||
.ok_or(Error)?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user