chia_sdk_driver/primitives/action_layer/
state_scheduler.rs1use 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 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 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 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}