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