chia_sdk_driver/primitives/action_layer/
state_scheduler.rs

1use chia_protocol::{Bytes32, Coin, CoinSpend};
2use chia_puzzle_types::singleton::{LauncherSolution, SingletonArgs, SingletonSolution};
3use chia_puzzle_types::{EveProof, LineageProof, Proof};
4use chia_puzzles::SINGLETON_LAUNCHER_HASH;
5use chia_sdk_types::puzzles::StateSchedulerLayerSolution;
6use clvm_traits::{FromClvm, ToClvm};
7use clvm_utils::ToTreeHash;
8use clvmr::{Allocator, NodePtr, serde::node_from_bytes};
9
10use crate::{DriverError, Layer, Spend, SpendContext, StateSchedulerInfo};
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct StateScheduler<S>
14where
15    S: ToTreeHash + Clone + ToClvm<Allocator> + FromClvm<Allocator>,
16{
17    pub coin: Coin,
18    pub proof: Proof,
19
20    pub info: StateSchedulerInfo<S>,
21}
22
23impl<S> StateScheduler<S>
24where
25    S: ToTreeHash + Clone + ToClvm<Allocator> + FromClvm<Allocator>,
26{
27    pub fn new(coin: Coin, proof: Proof, info: StateSchedulerInfo<S>) -> Self {
28        Self { coin, proof, info }
29    }
30
31    pub fn child(&self) -> Option<Self> {
32        // check for both self.info.generation and self.info.generation + 1 to be < self.info.state_schedule.len()
33        if self.info.generation + 1 >= self.info.state_schedule.len() {
34            return None;
35        }
36
37        let child_proof = Proof::Lineage(LineageProof {
38            parent_parent_coin_info: self.coin.parent_coin_info,
39            parent_inner_puzzle_hash: self.info.inner_puzzle_hash().into(),
40            parent_amount: self.coin.amount,
41        });
42
43        let child_info = self.info.clone().with_generation(self.info.generation + 1);
44        let child_inner_puzzle_hash = child_info.inner_puzzle_hash();
45
46        Some(Self {
47            coin: Coin::new(
48                self.coin.coin_id(),
49                SingletonArgs::curry_tree_hash(self.info.launcher_id, child_inner_puzzle_hash)
50                    .into(),
51                1,
52            ),
53            proof: child_proof,
54            info: child_info,
55        })
56    }
57
58    pub fn spend(
59        self,
60        ctx: &mut SpendContext,
61        other_singleton_inner_puzzle_hash: Bytes32,
62    ) -> Result<(), DriverError> {
63        let lineage_proof = self.proof;
64        let coin = self.coin;
65
66        let layers = self.info.into_layers();
67
68        let puzzle = layers.construct_puzzle(ctx)?;
69        let solution = layers.construct_solution(
70            ctx,
71            SingletonSolution {
72                lineage_proof,
73                amount: coin.amount,
74                inner_solution: StateSchedulerLayerSolution {
75                    other_singleton_inner_puzzle_hash,
76                    inner_solution: (),
77                },
78            },
79        )?;
80
81        ctx.spend(coin, Spend::new(puzzle, solution))?;
82
83        Ok(())
84    }
85
86    pub fn from_launcher_spend(
87        ctx: &mut SpendContext,
88        launcher_spend: &CoinSpend,
89    ) -> Result<Option<Self>, DriverError> {
90        if launcher_spend.coin.puzzle_hash != SINGLETON_LAUNCHER_HASH.into() {
91            return Ok(None);
92        }
93
94        let solution = node_from_bytes(ctx, &launcher_spend.solution)?;
95        let solution = ctx.extract::<LauncherSolution<NodePtr>>(solution)?;
96
97        let Some((info, _other_hints)) =
98            StateSchedulerInfo::from_launcher_solution::<NodePtr>(ctx, solution)?
99        else {
100            return Ok(None);
101        };
102
103        let new_coin = Coin::new(
104            launcher_spend.coin.coin_id(),
105            SingletonArgs::curry_tree_hash(info.launcher_id, info.inner_puzzle_hash()).into(),
106            1,
107        );
108
109        Ok(Some(Self::new(
110            new_coin,
111            Proof::Eve(EveProof {
112                parent_parent_coin_info: launcher_spend.coin.parent_coin_info,
113                parent_amount: launcher_spend.coin.amount,
114            }),
115            info,
116        )))
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use chia_puzzle_types::Memos;
123    use chia_sdk_test::Simulator;
124    use chia_sdk_types::Conditions;
125    use clvmr::NodePtr;
126
127    use crate::{CatalogRegistryState, Launcher, SingletonLayer, StateSchedulerLauncherHints};
128
129    use super::*;
130
131    fn mock_state(generator: u8) -> CatalogRegistryState {
132        CatalogRegistryState {
133            cat_maker_puzzle_hash: Bytes32::new([generator; 32]),
134            registration_price: u64::from(generator) * 1000,
135        }
136    }
137
138    #[test]
139    fn test_price_scheduler() -> anyhow::Result<()> {
140        let ctx = &mut SpendContext::new();
141        let mut sim = Simulator::new();
142
143        let schedule = vec![
144            (0, mock_state(0)),
145            (1, mock_state(1)),
146            (2, mock_state(2)),
147            (3, mock_state(3)),
148            (4, mock_state(4)),
149            (5, mock_state(5)),
150            (6, mock_state(6)),
151            (7, mock_state(7)),
152        ];
153        let final_puzzle_hash = "yakuhito".tree_hash().into();
154
155        // Launch 'other' singleton, which will consume (reveive) the messages
156        let other_singleton_inner_puzzle = ctx.alloc(&1)?;
157        let other_singleton_inner_puzzle_hash = ctx.tree_hash(other_singleton_inner_puzzle);
158
159        let other_singleton_launcher = sim.new_coin(SINGLETON_LAUNCHER_HASH.into(), 1);
160        let other_launcher = Launcher::new(other_singleton_launcher.parent_coin_info, 1);
161        let (_conds, mut other_singleton_coin) =
162            other_launcher.spend(ctx, other_singleton_inner_puzzle_hash.into(), ())?;
163
164        sim.spend_coins(ctx.take(), &[])?;
165
166        // Launch state scheduler singleton
167        let launcher_coin = sim.new_coin(SINGLETON_LAUNCHER_HASH.into(), 1);
168        let launcher = Launcher::new(launcher_coin.parent_coin_info, 1);
169
170        let first_coin_info = StateSchedulerInfo::new(
171            launcher_coin.coin_id(),
172            other_singleton_launcher.coin_id(),
173            schedule.clone(),
174            0,
175            final_puzzle_hash,
176        );
177        let (_conds, state_scheduler_coin) = launcher.spend(
178            ctx,
179            first_coin_info.inner_puzzle_hash().into(),
180            StateSchedulerLauncherHints {
181                my_launcher_id: launcher_coin.coin_id(),
182                receiver_singleton_launcher_id: other_singleton_launcher.coin_id(),
183                final_puzzle_hash,
184                state_schedule: schedule.clone(),
185                final_puzzle_hash_hints: NodePtr::NIL,
186            },
187        )?;
188
189        let spends = ctx.take();
190        assert_eq!(spends.len(), 1);
191        let state_scheduler_launcher_spend = spends[0].clone();
192        ctx.insert(state_scheduler_launcher_spend.clone());
193
194        sim.spend_coins(ctx.take(), &[])?;
195
196        let mut state_scheduler =
197            StateScheduler::from_launcher_spend(ctx, &state_scheduler_launcher_spend)?.unwrap();
198        assert_eq!(state_scheduler.info, first_coin_info);
199        assert_eq!(state_scheduler.coin, state_scheduler_coin);
200
201        let mut other_singleton_coin_parent = other_singleton_coin;
202        for (index, (block, new_state)) in schedule.iter().enumerate() {
203            state_scheduler
204                .clone()
205                .spend(ctx, other_singleton_inner_puzzle_hash.into())?;
206
207            let spends = ctx.take();
208            assert_eq!(spends.len(), 1);
209            let state_scheduler_spend = spends[0].clone();
210            ctx.insert(state_scheduler_spend.clone());
211
212            let other_singleton = SingletonLayer::<NodePtr>::new(
213                other_singleton_launcher.coin_id(),
214                other_singleton_inner_puzzle,
215            );
216            let other_singleton_lp = if index == 0 {
217                Proof::Eve(EveProof {
218                    parent_parent_coin_info: other_singleton_launcher.parent_coin_info,
219                    parent_amount: other_singleton_launcher.amount,
220                })
221            } else {
222                Proof::Lineage(LineageProof {
223                    parent_parent_coin_info: other_singleton_coin_parent.parent_coin_info,
224                    parent_inner_puzzle_hash: other_singleton_inner_puzzle_hash.into(),
225                    parent_amount: other_singleton_coin_parent.amount,
226                })
227            };
228            let state_scheduler_puzzle_hash_ptr = ctx.alloc(&state_scheduler.coin.puzzle_hash)?;
229            let other_singleton_inner_solution = ctx.alloc(
230                &Conditions::new()
231                    .receive_message(
232                        18,
233                        new_state.tree_hash().to_vec().into(),
234                        vec![state_scheduler_puzzle_hash_ptr],
235                    )
236                    .create_coin(other_singleton_inner_puzzle_hash.into(), 1, Memos::None),
237            )?;
238            let other_singleton_spend = other_singleton.construct_spend(
239                ctx,
240                SingletonSolution {
241                    lineage_proof: other_singleton_lp,
242                    amount: 1,
243                    inner_solution: other_singleton_inner_solution,
244                },
245            )?;
246
247            ctx.spend(other_singleton_coin, other_singleton_spend)?;
248            other_singleton_coin_parent = other_singleton_coin;
249            other_singleton_coin = Coin::new(
250                other_singleton_coin.coin_id(),
251                other_singleton_coin.puzzle_hash,
252                1,
253            );
254
255            sim.spend_coins(ctx.take(), &[])?;
256
257            if index < schedule.len() - 1 {
258                state_scheduler = state_scheduler.child().unwrap();
259
260                assert_eq!(state_scheduler.info.state_schedule, schedule);
261                assert_eq!(state_scheduler.info.generation, *block as usize + 1);
262            } else {
263                break;
264            }
265        }
266
267        Ok(())
268    }
269}