chik-sdk-driver 0.25.0

Driver code for interacting with standard puzzles on the Chik blockchain.
Documentation
use chik_protocol::Bytes32;
use chik_puzzle_types::{EveProof, Proof};
use chik_sdk_types::Conditions;
use klvm_traits::klvm_quote;
use klvm_utils::ToTreeHash;
use klvmr::NodePtr;

use crate::{DriverError, Launcher, Spend, SpendContext};

use super::{OptionContract, OptionInfo, OptionMetadata, OptionType, OptionUnderlying};

#[derive(Debug, Clone, Copy)]
pub struct UnspecifiedOption {
    owner_puzzle_hash: Bytes32,
    underlying: OptionUnderlying,
    metadata: OptionMetadata,
}

#[derive(Debug, Clone, Copy)]
pub struct ReadyOption {
    info: OptionInfo,
    metadata: OptionMetadata,
}

#[derive(Debug, Clone)]
pub struct OptionLauncher<S = UnspecifiedOption> {
    launcher: Launcher,
    state: S,
}

impl<S> OptionLauncher<S> {
    pub fn launcher(&self) -> &Launcher {
        &self.launcher
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct OptionLauncherInfo {
    pub creator_puzzle_hash: Bytes32,
    pub owner_puzzle_hash: Bytes32,
    pub seconds: u64,
    pub underlying_amount: u64,
    pub strike_type: OptionType,
}

impl OptionLauncherInfo {
    pub fn new(
        creator_puzzle_hash: Bytes32,
        owner_puzzle_hash: Bytes32,
        seconds: u64,
        underlying_amount: u64,
        strike_type: OptionType,
    ) -> Self {
        Self {
            creator_puzzle_hash,
            owner_puzzle_hash,
            seconds,
            underlying_amount,
            strike_type,
        }
    }
}

impl OptionLauncher<UnspecifiedOption> {
    pub fn new(
        ctx: &mut SpendContext,
        parent_coin_id: Bytes32,
        info: OptionLauncherInfo,
    ) -> Result<Self, DriverError> {
        Self::with_amount(ctx, parent_coin_id, 1, info)
    }

    pub fn with_amount(
        ctx: &mut SpendContext,
        parent_coin_id: Bytes32,
        amount: u64,
        info: OptionLauncherInfo,
    ) -> Result<Self, DriverError> {
        let memos = ctx.hint(info.creator_puzzle_hash)?;
        let launcher = Launcher::with_memos(parent_coin_id, amount, memos).with_singleton_amount(1);
        let launcher_id = launcher.coin().coin_id();

        Ok(Self {
            launcher,
            state: UnspecifiedOption {
                owner_puzzle_hash: info.owner_puzzle_hash,
                underlying: OptionUnderlying::new(
                    launcher_id,
                    info.creator_puzzle_hash,
                    info.seconds,
                    info.underlying_amount,
                    info.strike_type,
                ),
                metadata: OptionMetadata::new(info.seconds, info.strike_type),
            },
        })
    }

    pub fn underlying(&self) -> OptionUnderlying {
        self.state.underlying
    }

    pub fn p2_puzzle_hash(&self) -> Bytes32 {
        self.state.underlying.tree_hash().into()
    }

    pub fn metadata(&self) -> OptionMetadata {
        self.state.metadata
    }

    pub fn with_underlying(self, underlying_coin_id: Bytes32) -> OptionLauncher<ReadyOption> {
        let launcher_id = self.launcher.coin().coin_id();

        let info = OptionInfo::new(
            launcher_id,
            underlying_coin_id,
            self.state.underlying.delegated_puzzle().tree_hash().into(),
            self.state.owner_puzzle_hash,
        );

        OptionLauncher {
            launcher: self.launcher,
            state: ReadyOption {
                info,
                metadata: self.state.metadata,
            },
        }
    }
}

impl OptionLauncher<ReadyOption> {
    pub fn info(&self) -> OptionInfo {
        self.state.info
    }

    pub fn mint(self, ctx: &mut SpendContext) -> Result<(Conditions, OptionContract), DriverError> {
        let owner_puzzle_hash = self.state.info.p2_puzzle_hash;

        let memos = ctx.hint(owner_puzzle_hash)?;
        let conditions = Conditions::new().create_coin(owner_puzzle_hash, 1, memos);

        let inner_puzzle = ctx.alloc(&klvm_quote!(conditions))?;
        let eve_p2_puzzle_hash = ctx.tree_hash(inner_puzzle).into();
        let inner_spend = Spend::new(inner_puzzle, NodePtr::NIL);

        let (mint_eve_option, eve_option) = self.mint_eve(ctx, eve_p2_puzzle_hash)?;
        eve_option.spend(ctx, inner_spend)?;

        let child = eve_option.child(owner_puzzle_hash);

        Ok((mint_eve_option, child))
    }

    fn mint_eve(
        self,
        ctx: &mut SpendContext,
        p2_puzzle_hash: Bytes32,
    ) -> Result<(Conditions, OptionContract), DriverError> {
        let launcher_coin = self.launcher.coin();

        let info = self.state.info.with_p2_puzzle_hash(p2_puzzle_hash);

        let (launch_singleton, eve_coin) =
            self.launcher
                .spend(ctx, info.inner_puzzle_hash().into(), self.state.metadata)?;

        let proof = Proof::Eve(EveProof {
            parent_parent_coin_info: launcher_coin.parent_coin_info,
            parent_amount: launcher_coin.amount,
        });

        Ok((launch_singleton, OptionContract::new(eve_coin, proof, info)))
    }
}

#[cfg(test)]
mod tests {
    use chik_protocol::Coin;
    use chik_puzzle_types::Memos;
    use chik_sdk_test::Simulator;

    use crate::StandardLayer;

    use super::*;

    #[test]
    fn test_mint_option() -> anyhow::Result<()> {
        let mut sim = Simulator::new();
        let ctx = &mut SpendContext::new();

        let alice = sim.bls(1);
        let alice_p2 = StandardLayer::new(alice.pk);

        let parent_coin = sim.new_coin(alice.puzzle_hash, 1);

        let launcher = OptionLauncher::new(
            ctx,
            alice.coin.coin_id(),
            OptionLauncherInfo::new(
                alice.puzzle_hash,
                alice.puzzle_hash,
                10,
                1,
                OptionType::Xck { amount: 1 },
            ),
        )?;
        let p2_option = launcher.p2_puzzle_hash();

        alice_p2.spend(
            ctx,
            parent_coin,
            Conditions::new().create_coin(p2_option, 1, Memos::None),
        )?;
        let launcher =
            launcher.with_underlying(Coin::new(parent_coin.coin_id(), p2_option, 1).coin_id());

        let (mint_option, _option) = launcher.mint(ctx)?;
        alice_p2.spend(ctx, alice.coin, mint_option)?;

        sim.spend_coins(ctx.take(), &[alice.sk.clone()])?;

        Ok(())
    }
}