use ddk_dlc::secp256k1_zkp::Secp256k1;
use ddk_messages::OfferDlc;
use super::context::{ensure_protocol_version, validate_offer_funding_inputs};
use super::error::ContractError;
use super::types::{random_serial_id, random_temporary_contract_id, CreateOfferParams};
use super::PROTOCOL_VERSION;
pub fn create_offer(params: CreateOfferParams) -> Result<OfferDlc, ContractError> {
let CreateOfferParams {
chain_hash,
temporary_contract_id,
contract_info,
offer_collateral,
party,
fund_output_serial_id,
fee_rate_per_vb,
cet_locktime,
refund_locktime,
contract_flags,
} = params;
validate_offer_funding_inputs(&party.funding_inputs)?;
ddk_dlc::util::validate_fee_rate(fee_rate_per_vb)
.map_err(|e| ContractError::InvalidOffer(format!("invalid fee rate: {e}")))?;
if cet_locktime >= refund_locktime {
return Err(ContractError::InvalidOffer(
"refund locktime must be after the CET locktime".to_string(),
));
}
let mut input_serial_ids = party
.funding_inputs
.iter()
.map(|input| input.input_serial_id)
.collect::<Vec<_>>();
input_serial_ids.sort_unstable();
if input_serial_ids.windows(2).any(|pair| pair[0] == pair[1]) {
return Err(ContractError::InvalidFundingInput(
"funding input serial ids are not unique".to_string(),
));
}
let offer = OfferDlc {
protocol_version: PROTOCOL_VERSION,
contract_flags,
chain_hash,
temporary_contract_id: temporary_contract_id.unwrap_or_else(random_temporary_contract_id),
contract_info,
funding_pubkey: party.funding_pubkey,
payout_spk: party.payout_spk,
payout_serial_id: party.payout_serial_id.unwrap_or_else(random_serial_id),
offer_collateral,
funding_inputs: party.funding_inputs,
change_spk: party.change_spk,
change_serial_id: party.change_serial_id.unwrap_or_else(random_serial_id),
fund_output_serial_id: fund_output_serial_id.unwrap_or_else(random_serial_id),
fee_rate_per_vb,
cet_locktime,
refund_locktime,
};
if offer.offer_collateral > offer.get_total_collateral() {
return Err(ContractError::InvalidOffer(
"offer collateral exceeds total collateral".to_string(),
));
}
let execution_infos = ddk_manager::contract::execution_contract_infos(&offer.contract_info)?;
if execution_infos.is_empty() {
return Err(ContractError::InvalidOffer(
"contract does not contain execution information".to_string(),
));
}
for info in &execution_infos {
info.validate()?;
}
Ok(offer)
}
pub fn validate_offer(
offer: &OfferDlc,
min_timeout_interval: u32,
max_timeout_interval: u32,
) -> Result<(), ContractError> {
ensure_protocol_version(offer.protocol_version, ContractError::InvalidOffer)?;
validate_offer_funding_inputs(&offer.funding_inputs)?;
ddk_dlc::util::validate_fee_rate(offer.fee_rate_per_vb)
.map_err(|e| ContractError::InvalidOffer(format!("invalid fee rate: {e}")))?;
if offer.offer_collateral > offer.get_total_collateral() {
return Err(ContractError::InvalidOffer(
"offer collateral exceeds total collateral".to_string(),
));
}
offer
.validate(
&Secp256k1::verification_only(),
min_timeout_interval,
max_timeout_interval,
)
.map_err(|e| ContractError::InvalidOffer(e.to_string()))?;
Ok(())
}