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

1use chia_protocol::Bytes32;
2use chia_puzzle_types::singleton::SingletonStruct;
3use chia_sdk_types::{
4    Conditions, Mod, announcement_id,
5    puzzles::{
6        DefaultCatMakerArgs, PrecommitSpendMode, XchandlesRefundActionArgs,
7        XchandlesRefundActionSolution, XchandlesSlotValue,
8    },
9};
10use clvm_traits::{FromClvm, ToClvm};
11use clvm_utils::{ToTreeHash, TreeHash};
12use clvmr::NodePtr;
13
14use crate::{
15    DriverError, PrecommitCoin, PrecommitLayer, SingletonAction, Slot, Spend, SpendContext,
16    XchandlesConstants, XchandlesPrecommitValue, XchandlesRegistry,
17};
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub struct XchandlesRefundAction {
21    pub launcher_id: Bytes32,
22    pub relative_block_height: u32,
23    pub payout_puzzle_hash: Bytes32,
24}
25
26impl ToTreeHash for XchandlesRefundAction {
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<XchandlesRegistry> for XchandlesRefundAction {
38    fn from_constants(constants: &XchandlesConstants) -> 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 XchandlesRefundAction {
48    pub fn new_args(
49        launcher_id: Bytes32,
50        relative_block_height: u32,
51        payout_puzzle_hash: Bytes32,
52    ) -> XchandlesRefundActionArgs {
53        XchandlesRefundActionArgs {
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::<()>::first_curry_hash(launcher_id, 0).into(),
61        }
62    }
63
64    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        ctx: &SpendContext,
74        solution: NodePtr,
75    ) -> Result<Option<XchandlesSlotValue>, DriverError> {
76        let solution =
77            XchandlesRefundActionSolution::<NodePtr, NodePtr, NodePtr, NodePtr, NodePtr>::from_clvm(
78                ctx, solution,
79            )?;
80
81        Ok(solution.slot_value)
82    }
83
84    pub fn created_slot_value(
85        spent_slot_value: Option<XchandlesSlotValue>,
86    ) -> Option<XchandlesSlotValue> {
87        spent_slot_value // nothing changed; just oracle
88    }
89
90    pub fn spend(
91        self,
92        ctx: &mut SpendContext,
93        registry: &mut XchandlesRegistry,
94        precommit_coin: &PrecommitCoin<XchandlesPrecommitValue>,
95        precommited_pricing_puzzle_reveal: NodePtr,
96        precommited_pricing_puzzle_solution: NodePtr,
97        slot: Option<Slot<XchandlesSlotValue>>,
98    ) -> Result<Conditions, DriverError> {
99        // calculate announcement
100        let mut refund_announcement = precommit_coin.coin.puzzle_hash.to_vec();
101        refund_announcement.insert(0, b'$');
102
103        // spend precommit coin
104        let my_inner_puzzle_hash = registry.info.inner_puzzle_hash().into();
105        precommit_coin.spend(ctx, PrecommitSpendMode::REFUND, my_inner_puzzle_hash)?;
106
107        // spend self
108        let slot = slot.map(|s| registry.actual_slot(s));
109        let cat_maker_args = DefaultCatMakerArgs::new(precommit_coin.asset_id.tree_hash().into());
110        let action_solution = XchandlesRefundActionSolution {
111            precommited_cat_maker_reveal: ctx.curry(cat_maker_args)?,
112            precommited_cat_maker_hash: cat_maker_args.curry_tree_hash().into(),
113            precommited_cat_maker_solution: (),
114            precommited_pricing_puzzle_reveal,
115            precommited_pricing_puzzle_hash: ctx
116                .tree_hash(precommited_pricing_puzzle_reveal)
117                .into(),
118            precommited_pricing_puzzle_solution,
119            handle: precommit_coin.value.handle.clone(),
120            secret: precommit_coin.value.secret,
121            precommited_owner_launcher_id: precommit_coin.value.owner_launcher_id,
122            precommited_resolved_data: precommit_coin.value.resolved_data.clone(),
123            refund_puzzle_hash_hash: precommit_coin.refund_puzzle_hash.tree_hash().into(),
124            precommit_amount: precommit_coin.coin.amount,
125            slot_value: slot.as_ref().map(|slot| slot.info.value.clone()),
126        }
127        .to_clvm(ctx)?;
128        let action_puzzle = self.construct_puzzle(ctx)?;
129
130        registry.insert_action_spend(ctx, Spend::new(action_puzzle, action_solution))?;
131
132        // if there's a slot, spend it
133        if let Some(slot) = slot {
134            slot.spend(ctx, my_inner_puzzle_hash)?;
135        }
136
137        Ok(
138            Conditions::new().assert_puzzle_announcement(announcement_id(
139                registry.coin.puzzle_hash,
140                refund_announcement,
141            )),
142        )
143    }
144}