ddk 1.0.11

application tooling for DLCs 🌊
Documentation
mod test_util;

use bitcoin::Amount;
use chrono::{Local, TimeDelta};
use ddk::{
    logger::{LogLevel, Logger},
    Transport,
};
use ddk_manager::{contract::Contract, Storage};
use ddk_messages::Message;
use ddk_payouts::options::{Direction, OptionType};
use std::{
    sync::Arc,
    time::{Duration, SystemTime, UNIX_EPOCH},
};
use test_util::{generate_blocks, test_ddk};
use tokio::time::sleep;

// #[tokio::test]
#[test_log::test(tokio::test)]
#[ignore]
async fn short_call() {
    dotenv::dotenv().ok();
    let logger_one = Arc::new(Logger::tracing("alice".to_string(), LogLevel::Info));
    let logger_two = Arc::new(Logger::disabled("bob".to_string()));
    let (alice, bob, oracle) = test_ddk(logger_one, logger_two).await;
    let expiry = TimeDelta::seconds(15);
    let timestamp: u32 = Local::now()
        .checked_add_signed(expiry)
        .unwrap()
        .timestamp()
        .try_into()
        .unwrap();
    let event_id = uuid::Uuid::new_v4().to_string();

    let announcement = oracle
        .oracle
        .create_numeric_event(event_id, 20, false, 2, "BTC/USD".to_string(), timestamp)
        .await
        .unwrap();

    let contract_input = ddk_payouts::options::build_option_order_offer(
        &announcement,
        Amount::ONE_BTC,
        50_000,
        Amount::from_sat(500_000),
        1,
        1_000,
        OptionType::Call,
        Direction::Short,
        Amount::from_sat(100_500_000),
        20,
    )
    .unwrap();

    let offer = alice
        .ddk
        .manager
        .send_offer_with_announcements(
            &contract_input,
            bob.ddk.transport.public_key(),
            vec![vec![announcement.clone()]],
        )
        .await
        .unwrap();

    let contract_id = offer.temporary_contract_id.clone();

    bob.ddk
        .manager
        .on_dlc_message(
            &Message::Offer(offer),
            alice.ddk.transport.public_key().clone(),
        )
        .await
        .unwrap();

    let accept = bob.ddk.manager.accept_contract_offer(&contract_id).await;

    let (contract_id, _counterparty, accept_dlc) = accept.unwrap();

    let alice_sign = alice
        .ddk
        .manager
        .on_dlc_message(
            &Message::Accept(accept_dlc),
            bob.ddk.transport.public_key().clone(),
        )
        .await
        .unwrap();

    bob.ddk
        .manager
        .on_dlc_message(
            &alice_sign.unwrap(),
            alice.ddk.transport.public_key().clone(),
        )
        .await
        .unwrap();

    generate_blocks(10);

    alice
        .ddk
        .manager
        .periodic_check(false)
        .await
        .expect("alice check failed");

    bob.ddk
        .manager
        .periodic_check(false)
        .await
        .expect("bob check failed");

    let contract = bob.ddk.storage.get_contract(&contract_id).await.unwrap();
    assert!(matches!(contract.unwrap(), Contract::Confirmed(_)));

    bob.ddk.wallet.sync().await.unwrap();
    alice.ddk.wallet.sync().await.unwrap();

    // Used to check that timelock is reached.
    let locktime = match alice.ddk.storage.get_contract(&contract_id).await.unwrap() {
        Some(contract) => match contract {
            Contract::Confirmed(signed_contract) => {
                signed_contract.accepted_contract.dlc_transactions.cets[0]
                    .lock_time
                    .to_consensus_u32()
            }
            _ => unreachable!("No locktime."),
        },
        None => unreachable!("No locktime"),
    };

    let mut time = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_secs() as u32;

    let attestation = alice
        .ddk
        .oracle
        .oracle
        .sign_numeric_event(announcement.oracle_event.event_id.clone(), 53_000)
        .await;

    assert!(attestation.is_ok());

    while time < announcement.clone().oracle_event.event_maturity_epoch || time < locktime {
        let checked_time = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs() as u32;

        time = checked_time;
        generate_blocks(5);
    }

    bob.ddk.wallet.sync().await.unwrap();
    alice.ddk.wallet.sync().await.unwrap();

    bob.ddk
        .manager
        .close_confirmed_contract(&contract_id, vec![(0, attestation.unwrap())])
        .await
        .unwrap();

    sleep(Duration::from_secs(10)).await;

    let contract = bob
        .ddk
        .storage
        .get_contract(&contract_id)
        .await
        .unwrap()
        .unwrap();
    assert!(matches!(contract, Contract::PreClosed(_)));

    generate_blocks(10);

    bob.ddk.manager.periodic_check(false).await.unwrap();
    alice.ddk.manager.periodic_check(false).await.unwrap();

    let contract = bob
        .ddk
        .storage
        .get_contract(&contract_id)
        .await
        .unwrap()
        .unwrap();
    assert!(matches!(contract, Contract::Closed(_)));

    let bob_contract = bob
        .ddk
        .storage
        .get_contract(&contract_id)
        .await
        .unwrap()
        .unwrap();
    let alice_contract = alice
        .ddk
        .storage
        .get_contract(&contract_id)
        .await
        .unwrap()
        .unwrap();

    match bob_contract {
        Contract::Closed(c) => println!("Bob: {} sats", c.pnl),
        _ => assert!(false),
    }

    match alice_contract {
        Contract::Closed(c) => println!("Alice: {} sats", c.pnl),
        _ => assert!(false),
    }
}