pub(crate) mod fees;
pub(crate) mod outcome;
pub(crate) mod split;
use bitcoin::{transaction::InputWeightPrediction, Amount, FeeRate, TxOut};
use secp::Point;
use serde::{Deserialize, Serialize};
use crate::{
consts::{P2TR_DUST_VALUE, P2TR_SCRIPT_PUBKEY_SIZE},
errors::Error,
oracles::EventLockingConditions,
parties::{MarketMaker, Player},
spend_info::FundingSpendInfo,
};
use std::collections::{BTreeMap, BTreeSet};
pub type PlayerIndex = usize;
pub type OutcomeIndex = usize;
pub type PayoutWeights = BTreeMap<PlayerIndex, u64>;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ContractParameters {
pub market_maker: MarketMaker,
pub players: Vec<Player>,
pub event: EventLockingConditions,
pub outcome_payouts: BTreeMap<Outcome, PayoutWeights>,
pub fee_rate: FeeRate,
pub funding_value: Amount,
pub relative_locktime_block_delta: u16,
}
#[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub enum Outcome {
Attestation(OutcomeIndex),
Expiry,
}
#[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct WinCondition {
pub outcome: Outcome,
pub player_index: PlayerIndex,
}
impl ContractParameters {
pub fn validate(&self) -> Result<(), Error> {
let uniq_ticket_hashes: BTreeSet<&[u8; 32]> = self
.players
.iter()
.map(|player| &player.ticket_hash)
.collect();
if uniq_ticket_hashes.len() != self.players.len() {
return Err(Error);
}
for (outcome, payout_map) in self.outcome_payouts.iter() {
if !self.event.is_valid_outcome(outcome) {
return Err(Error);
}
if payout_map.len() == 0 {
return Err(Error);
}
for (&player_index, &weight) in payout_map.iter() {
if weight == 0 {
return Err(Error);
}
if player_index >= self.players.len() {
return Err(Error);
}
}
}
if self.fee_rate == FeeRate::ZERO {
return Err(Error);
}
if self.relative_locktime_block_delta == 0 {
return Err(Error);
}
if self.funding_value < Amount::ZERO {
return Err(Error);
}
Ok(())
}
pub fn funding_output(&self) -> Result<TxOut, Error> {
let spend_info =
FundingSpendInfo::new(&self.market_maker, &self.players, self.funding_value)?;
Ok(spend_info.funding_output())
}
pub(crate) fn outcome_output_value(&self) -> Result<Amount, Error> {
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)
}
pub fn players_controlled_by_pubkey(&self, pubkey: Point) -> BTreeSet<PlayerIndex> {
self.players
.iter()
.enumerate()
.filter_map(|(i, player)| {
if player.pubkey == pubkey {
Some(i)
} else {
None
}
})
.collect()
}
pub fn win_conditions_claimable_by_pubkey(
&self,
pubkey: Point,
) -> Option<BTreeSet<WinCondition>> {
let is_market_maker = pubkey == self.market_maker.pubkey;
let controlling_players = self.players_controlled_by_pubkey(pubkey);
if controlling_players.is_empty() && !is_market_maker {
return None;
}
let mut relevant_win_conditions = BTreeSet::<WinCondition>::new();
for (&outcome, payout_map) in self.outcome_payouts.iter() {
relevant_win_conditions.extend(payout_map.keys().filter_map(|player_index| {
if is_market_maker || controlling_players.contains(player_index) {
Some(WinCondition {
player_index: *player_index,
outcome,
})
} else {
None
}
}));
}
Some(relevant_win_conditions)
}
pub fn win_conditions_controlled_by_pubkey(
&self,
pubkey: Point,
) -> Option<BTreeSet<WinCondition>> {
let is_market_maker = pubkey == self.market_maker.pubkey;
let controlling_players = self.players_controlled_by_pubkey(pubkey);
if controlling_players.is_empty() && !is_market_maker {
return None;
}
let mut win_conditions_to_sign = BTreeSet::<WinCondition>::new();
for (&outcome, payout_map) in self.outcome_payouts.iter() {
if is_market_maker
|| controlling_players
.iter()
.any(|player_index| payout_map.contains_key(player_index))
{
win_conditions_to_sign.extend(payout_map.keys().map(|&player_index| {
WinCondition {
player_index,
outcome,
}
}));
}
}
Some(win_conditions_to_sign)
}
pub fn sigmap_for_pubkey(&self, pubkey: Point) -> Option<SigMap<()>> {
let win_conditions = self.win_conditions_controlled_by_pubkey(pubkey)?;
let sigmap = SigMap {
by_outcome: self
.outcome_payouts
.iter()
.map(|(&outcome, _)| (outcome, ()))
.collect(),
by_win_condition: win_conditions.into_iter().map(|w| (w, ())).collect(),
};
Some(sigmap)
}
pub fn all_win_conditions(&self) -> BTreeSet<WinCondition> {
let mut all_win_conditions = BTreeSet::new();
for (&outcome, payout_map) in self.outcome_payouts.iter() {
all_win_conditions.extend(payout_map.keys().map(|&player_index| WinCondition {
player_index,
outcome,
}));
}
all_win_conditions
}
pub fn full_sigmap(&self) -> SigMap<()> {
SigMap {
by_outcome: self
.outcome_payouts
.iter()
.map(|(&outcome, _)| (outcome, ()))
.collect(),
by_win_condition: self
.all_win_conditions()
.into_iter()
.map(|win_cond| (win_cond, ()))
.collect(),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Default, Serialize, Deserialize)]
pub struct SigMap<T> {
pub by_outcome: BTreeMap<Outcome, T>,
pub by_win_condition: BTreeMap<WinCondition, T>,
}
impl<T> SigMap<T> {
pub fn map<V, F1, F2>(self, map_outcomes: F1, map_win_conditions: F2) -> SigMap<V>
where
F1: Fn(Outcome, T) -> V,
F2: Fn(WinCondition, T) -> V,
{
SigMap {
by_outcome: self
.by_outcome
.into_iter()
.map(|(o, t)| (o, map_outcomes(o, t)))
.collect(),
by_win_condition: self
.by_win_condition
.into_iter()
.map(|(w, t)| (w, map_win_conditions(w, t)))
.collect(),
}
}
pub fn map_values<V, F>(self, mut map_fn: F) -> SigMap<V>
where
F: FnMut(T) -> V,
{
SigMap {
by_outcome: self
.by_outcome
.into_iter()
.map(|(o, t)| (o, map_fn(t)))
.collect(),
by_win_condition: self
.by_win_condition
.into_iter()
.map(|(w, t)| (w, map_fn(t)))
.collect(),
}
}
pub fn by_ref(&self) -> SigMap<&T> {
SigMap {
by_outcome: self.by_outcome.iter().map(|(&k, v)| (k, v)).collect(),
by_win_condition: self.by_win_condition.iter().map(|(&k, v)| (k, v)).collect(),
}
}
pub fn is_mirror<V>(&self, other: &SigMap<V>) -> bool {
for outcome in self.by_outcome.keys() {
if !other.by_outcome.contains_key(outcome) {
return false;
}
}
for win_cond in self.by_win_condition.keys() {
if !other.by_win_condition.contains_key(win_cond) {
return false;
}
}
if self.by_outcome.len() != other.by_outcome.len()
|| self.by_win_condition.len() != other.by_win_condition.len()
{
return false;
}
true
}
}