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

1use chia_protocol::Bytes32;
2use chia_puzzle_types::offer::{NotarizedPayment, Payment};
3use chia_puzzles::SETTLEMENT_PAYMENT_HASH;
4use chia_sdk_types::{
5    announcement_id,
6    puzzles::{
7        DefaultCatMakerArgs, XchandlesExtendActionArgs, XchandlesExtendActionSolution,
8        XchandlesFactorPricingPuzzleArgs, XchandlesPricingSolution, XchandlesSlotValue,
9    },
10    Conditions, Mod,
11};
12use clvm_traits::{clvm_tuple, FromClvm};
13use clvm_utils::{ToTreeHash, TreeHash};
14use clvmr::NodePtr;
15
16use crate::{
17    DriverError, SingletonAction, Slot, Spend, SpendContext, XchandlesConstants, XchandlesRegistry,
18};
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub struct XchandlesExtendAction {
22    pub launcher_id: Bytes32,
23    pub payout_puzzle_hash: Bytes32,
24}
25
26impl ToTreeHash for XchandlesExtendAction {
27    fn tree_hash(&self) -> TreeHash {
28        Self::new_args(self.launcher_id, self.payout_puzzle_hash).curry_tree_hash()
29    }
30}
31
32impl SingletonAction<XchandlesRegistry> for XchandlesExtendAction {
33    fn from_constants(constants: &XchandlesConstants) -> Self {
34        Self {
35            launcher_id: constants.launcher_id,
36            payout_puzzle_hash: constants.precommit_payout_puzzle_hash,
37        }
38    }
39}
40
41impl XchandlesExtendAction {
42    pub fn new_args(
43        launcher_id: Bytes32,
44        payout_puzzle_hash: Bytes32,
45    ) -> XchandlesExtendActionArgs {
46        XchandlesExtendActionArgs {
47            offer_mod_hash: SETTLEMENT_PAYMENT_HASH.into(),
48            payout_puzzle_hash,
49            slot_1st_curry_hash: Slot::<()>::first_curry_hash(launcher_id, 0).into(),
50        }
51    }
52
53    fn construct_puzzle(&self, ctx: &mut SpendContext) -> Result<NodePtr, DriverError> {
54        ctx.curry(Self::new_args(self.launcher_id, self.payout_puzzle_hash))
55    }
56
57    pub fn spent_slot_value(
58        ctx: &mut SpendContext,
59        solution: NodePtr,
60    ) -> Result<XchandlesSlotValue, DriverError> {
61        let solution = ctx.extract::<XchandlesExtendActionSolution<
62            NodePtr,
63            (u64, (u64, (String, NodePtr))),
64            NodePtr,
65            NodePtr,
66        >>(solution)?;
67
68        // current expiration is the second truth given to a pricing puzzle
69        let current_expiration = solution.pricing_solution.1 .0;
70
71        Ok(XchandlesSlotValue::new(
72            solution.pricing_solution.1 .1 .0.tree_hash().into(),
73            solution.neighbors.left_value,
74            solution.neighbors.right_value,
75            current_expiration,
76            solution.rest.owner_launcher_id,
77            solution.rest.resolved_data,
78        ))
79    }
80
81    pub fn created_slot_value(
82        ctx: &mut SpendContext,
83        solution: NodePtr,
84    ) -> Result<XchandlesSlotValue, DriverError> {
85        let solution = ctx
86            .extract::<XchandlesExtendActionSolution<NodePtr, NodePtr, NodePtr, NodePtr>>(
87                solution,
88            )?;
89
90        let pricing_output = ctx.run(solution.pricing_puzzle_reveal, solution.pricing_solution)?;
91        let registration_time_delta = <(NodePtr, u64)>::from_clvm(ctx, pricing_output)?.1;
92
93        let (_, (_, (handle, _))) =
94            ctx.extract::<(NodePtr, (NodePtr, (String, NodePtr)))>(solution.pricing_solution)?;
95
96        // current expiration is the second truth given to a pricing puzzle
97        let current_expiration = ctx
98            .extract::<(NodePtr, (u64, NodePtr))>(solution.pricing_solution)?
99            .1
100             .0;
101
102        Ok(XchandlesSlotValue::new(
103            handle.tree_hash().into(),
104            solution.neighbors.left_value,
105            solution.neighbors.right_value,
106            current_expiration + registration_time_delta,
107            solution.rest.owner_launcher_id,
108            solution.rest.resolved_data,
109        ))
110    }
111
112    #[allow(clippy::too_many_arguments)]
113    pub fn spend(
114        self,
115        ctx: &mut SpendContext,
116        registry: &mut XchandlesRegistry,
117        handle: String,
118        slot: Slot<XchandlesSlotValue>,
119        payment_asset_id: Bytes32,
120        base_handle_price: u64,
121        registration_period: u64,
122        num_periods: u64,
123        buy_time: u64,
124    ) -> Result<(Conditions, NotarizedPayment), DriverError> {
125        let spender_inner_puzzle_hash = registry.info.inner_puzzle_hash().into();
126
127        // spend self
128        let cat_maker_puzzle_reveal = ctx.curry(DefaultCatMakerArgs::new(
129            payment_asset_id.tree_hash().into(),
130        ))?;
131        let pricing_puzzle_reveal = ctx.curry(XchandlesFactorPricingPuzzleArgs {
132            base_price: base_handle_price,
133            registration_period,
134        })?;
135
136        let slot = registry.actual_slot(slot);
137        let action_solution = ctx.alloc(&XchandlesExtendActionSolution {
138            pricing_puzzle_reveal,
139            pricing_solution: XchandlesPricingSolution {
140                buy_time,
141                current_expiration: slot.info.value.expiration,
142                handle: handle.clone(),
143                num_periods,
144            },
145            cat_maker_puzzle_reveal,
146            cat_maker_solution: (),
147            neighbors: slot.info.value.neighbors,
148            rest: slot.info.value.rest_data(),
149        })?;
150        let action_puzzle = self.construct_puzzle(ctx)?;
151
152        registry.insert_action_spend(ctx, Spend::new(action_puzzle, action_solution))?;
153
154        let renew_amount =
155            XchandlesFactorPricingPuzzleArgs::get_price(base_handle_price, &handle, num_periods);
156
157        let notarized_payment = NotarizedPayment {
158            nonce: clvm_tuple!(handle.clone(), slot.info.value.expiration)
159                .tree_hash()
160                .into(),
161            payments: vec![Payment::new(
162                registry.info.constants.precommit_payout_puzzle_hash,
163                renew_amount,
164                ctx.hint(registry.info.constants.precommit_payout_puzzle_hash)?,
165            )],
166        };
167
168        // spend slot
169        slot.spend(ctx, spender_inner_puzzle_hash)?;
170
171        let mut extend_ann = clvm_tuple!(renew_amount, handle).tree_hash().to_vec();
172        extend_ann.insert(0, b'e');
173
174        Ok((
175            Conditions::new()
176                .assert_puzzle_announcement(announcement_id(registry.coin.puzzle_hash, extend_ann)),
177            notarized_payment,
178        ))
179    }
180}