chia-sdk-driver 0.33.0

Driver code for interacting with standard puzzles on the Chia blockchain.
Documentation
use crate::{Deltas, DriverError, Id, SingletonDestination, SpendAction, SpendContext, Spends};

#[derive(Debug, Clone, Copy)]
pub struct MeltSingletonAction {
    pub id: Id,
    pub amount: u64,
}

impl MeltSingletonAction {
    pub fn new(id: Id, amount: u64) -> Self {
        Self { id, amount }
    }
}

impl SpendAction for MeltSingletonAction {
    fn calculate_delta(&self, deltas: &mut Deltas, _index: usize) {
        deltas.set_needed(self.id);
        deltas.update(self.id).output += self.amount;
        deltas.update(Id::Xch).input += self.amount;
    }

    fn spend(
        &self,
        _ctx: &mut SpendContext,
        spends: &mut Spends,
        _index: usize,
    ) -> Result<(), DriverError> {
        if let Some(did) = spends.dids.get_mut(&self.id) {
            let source = did.last_mut()?;
            source.child_info.destination = Some(SingletonDestination::Melt);
        } else if let Some(option) = spends.options.get_mut(&self.id) {
            let source = option.last_mut()?;
            source.child_info.destination = Some(SingletonDestination::Melt);
        } else {
            return Err(DriverError::InvalidAssetId);
        }

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use anyhow::Result;
    use chia_protocol::Bytes32;
    use chia_puzzle_types::{
        Memos,
        offer::{NotarizedPayment, Payment},
    };
    use chia_sdk_test::Simulator;
    use indexmap::indexmap;
    use rstest::rstest;

    use crate::{Action, Cat, CatSpend, OptionType, OptionUnderlying, Relation, SingletonInfo};

    use super::*;

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

        let alice = sim.bls(1);

        let mut spends = Spends::new(alice.puzzle_hash);
        spends.add(alice.coin);

        let deltas = spends.apply(
            &mut ctx,
            &[
                Action::create_empty_did(),
                Action::melt_singleton(Id::New(0), 1),
            ],
        )?;

        spends.finish_with_keys(
            &mut ctx,
            &deltas,
            Relation::None,
            &indexmap! { alice.puzzle_hash => alice.pk },
        )?;

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

        Ok(())
    }

    #[rstest]
    #[case::normal(None)]
    #[case::revocable(Some(Bytes32::default()))]
    fn test_action_exercise_option(#[case] hidden_puzzle_hash: Option<Bytes32>) -> Result<()> {
        let mut sim = Simulator::new();
        let mut ctx = SpendContext::new();

        let alice = sim.bls(2);
        let bob = sim.bls(1);
        let bob_hint = ctx.hint(bob.puzzle_hash)?;

        let mut spends = Spends::new(alice.puzzle_hash);
        spends.add(alice.coin);

        let deltas = spends.apply(
            &mut ctx,
            &[
                Action::single_issue_cat(hidden_puzzle_hash, 1),
                Action::mint_option(
                    alice.puzzle_hash,
                    10,
                    Id::New(0),
                    1,
                    OptionType::Xch { amount: 1 },
                    1,
                ),
                Action::send(Id::New(1), bob.puzzle_hash, 1, bob_hint),
            ],
        )?;

        let outputs = spends.finish_with_keys(
            &mut ctx,
            &deltas,
            Relation::None,
            &indexmap! { alice.puzzle_hash => alice.pk },
        )?;

        let underlying_cat = outputs.cats[&Id::New(0)][0];
        let option = outputs.options[&Id::New(1)];

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

        let underlying = OptionUnderlying::new(
            option.info.launcher_id,
            alice.puzzle_hash,
            10,
            1,
            OptionType::Xch { amount: 1 },
        );

        let underlying_spend =
            underlying.exercise_spend(&mut ctx, option.info.inner_puzzle_hash().into(), 1)?;

        let settlement_cats =
            Cat::spend_all(&mut ctx, &[CatSpend::new(underlying_cat, underlying_spend)])?;

        let mut spends = Spends::new(bob.puzzle_hash);
        spends.add(bob.coin);
        spends.add(option);
        spends.add(settlement_cats[0]);

        let deltas = spends.apply(
            &mut ctx,
            &[
                Action::melt_singleton(Id::Existing(option.info.launcher_id), 1),
                Action::settle(
                    Id::Xch,
                    NotarizedPayment::new(
                        option.info.launcher_id,
                        vec![Payment::new(alice.puzzle_hash, 1, Memos::None)],
                    ),
                ),
            ],
        )?;

        spends.finish_with_keys(
            &mut ctx,
            &deltas,
            Relation::None,
            &indexmap! { bob.puzzle_hash => bob.pk },
        )?;

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

        Ok(())
    }
}