chia_sdk_driver/layers/action_layer/actions/catalog/
refund.rs

1use chia_protocol::Bytes32;
2use chia_puzzle_types::singleton::SingletonStruct;
3use chia_sdk_types::{
4    Conditions, Mod, announcement_id,
5    puzzles::{
6        CatalogRefundActionArgs, CatalogRefundActionSolution, CatalogSlotValue,
7        DefaultCatMakerArgs, PrecommitSpendMode, SlotNeigborsInfo,
8    },
9};
10use clvm_traits::{FromClvm, ToClvm, clvm_tuple};
11use clvm_utils::{ToTreeHash, TreeHash};
12use clvmr::NodePtr;
13
14use crate::{
15    CatalogPrecommitValue, CatalogRegistry, CatalogRegistryConstants, DriverError, PrecommitCoin,
16    PrecommitLayer, SingletonAction, Slot, Spend, SpendContext,
17};
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub struct CatalogRefundAction {
21    pub launcher_id: Bytes32,
22    pub relative_block_height: u32,
23    pub payout_puzzle_hash: Bytes32,
24}
25
26impl ToTreeHash for CatalogRefundAction {
27    fn tree_hash(&self) -> TreeHash {
28        Self::new_args(
29            self.launcher_id,
30            self.relative_block_height,
31            self.payout_puzzle_hash,
32        )
33        .curry_tree_hash()
34    }
35}
36
37impl SingletonAction<CatalogRegistry> for CatalogRefundAction {
38    fn from_constants(constants: &CatalogRegistryConstants) -> Self {
39        Self {
40            launcher_id: constants.launcher_id,
41            relative_block_height: constants.relative_block_height,
42            payout_puzzle_hash: constants.precommit_payout_puzzle_hash,
43        }
44    }
45}
46
47impl CatalogRefundAction {
48    pub fn new_args(
49        launcher_id: Bytes32,
50        relative_block_height: u32,
51        payout_puzzle_hash: Bytes32,
52    ) -> CatalogRefundActionArgs {
53        CatalogRefundActionArgs {
54            precommit_1st_curry_hash: PrecommitLayer::<()>::first_curry_hash(
55                SingletonStruct::new(launcher_id).tree_hash().into(),
56                relative_block_height,
57                payout_puzzle_hash,
58            )
59            .into(),
60            slot_1st_curry_hash: Slot::<CatalogSlotValue>::first_curry_hash(launcher_id, 0).into(),
61        }
62    }
63
64    pub fn construct_puzzle(&self, ctx: &mut SpendContext) -> Result<NodePtr, DriverError> {
65        ctx.curry(Self::new_args(
66            self.launcher_id,
67            self.relative_block_height,
68            self.payout_puzzle_hash,
69        ))
70    }
71
72    pub fn spent_slot_value(
73        &self,
74        ctx: &SpendContext,
75        solution: NodePtr,
76    ) -> Result<Option<CatalogSlotValue>, DriverError> {
77        let params = CatalogRefundActionSolution::<NodePtr, ()>::from_clvm(ctx, solution)?;
78
79        Ok(params.neighbors.map(|neighbors| CatalogSlotValue {
80            asset_id: params.tail_hash,
81            neighbors,
82        }))
83    }
84
85    pub fn created_slot_value(
86        &self,
87        ctx: &SpendContext,
88        solution: NodePtr,
89    ) -> Result<Option<CatalogSlotValue>, DriverError> {
90        self.spent_slot_value(ctx, solution)
91    }
92
93    pub fn spend(
94        self,
95        ctx: &mut SpendContext,
96        catalog: &mut CatalogRegistry,
97        tail_hash: Bytes32,
98        neighbors: Option<SlotNeigborsInfo>,
99        precommit_coin: &PrecommitCoin<CatalogPrecommitValue>,
100        slot: Option<Slot<CatalogSlotValue>>,
101    ) -> Result<Conditions, DriverError> {
102        // calculate announcement
103        let mut refund_announcement =
104            clvm_tuple!(tail_hash, precommit_coin.value.initial_inner_puzzle_hash)
105                .tree_hash()
106                .to_vec();
107        refund_announcement.insert(0, b'$');
108
109        let secure_conditions = Conditions::new().assert_puzzle_announcement(announcement_id(
110            catalog.coin.puzzle_hash,
111            refund_announcement,
112        ));
113
114        // spend precommit coin
115        let spender_inner_puzzle_hash = catalog.info.inner_puzzle_hash().into();
116        let initial_inner_puzzle_hash = precommit_coin.value.initial_inner_puzzle_hash;
117        precommit_coin.spend(ctx, PrecommitSpendMode::REFUND, spender_inner_puzzle_hash)?;
118
119        // if there's a slot, spend it
120        if let Some(slot) = slot {
121            let slot = catalog.actual_slot(slot);
122            slot.spend(ctx, spender_inner_puzzle_hash)?;
123        }
124
125        // then, create action spend
126        let cat_maker_args = DefaultCatMakerArgs::new(precommit_coin.asset_id.tree_hash().into());
127        let action_solution = CatalogRefundActionSolution {
128            precommited_cat_maker_reveal: ctx.curry(cat_maker_args)?,
129            precommited_cat_maker_hash: cat_maker_args.curry_tree_hash().into(),
130            precommited_cat_maker_solution: (),
131            tail_hash,
132            initial_nft_owner_ph: initial_inner_puzzle_hash,
133            refund_puzzle_hash_hash: precommit_coin.refund_puzzle_hash.tree_hash().into(),
134            precommit_amount: precommit_coin.coin.amount,
135            neighbors,
136        };
137        let action_solution = action_solution.to_clvm(ctx)?;
138        let action_puzzle = self.construct_puzzle(ctx)?;
139
140        catalog.insert_action_spend(ctx, Spend::new(action_puzzle, action_solution))?;
141        Ok(secure_conditions)
142    }
143}