1use chia_bls::Signature;
2use chia_protocol::{Bytes32, Coin, CoinSpend, SpendBundle};
3use chia_puzzle_types::cat::CatSolution;
4use chia_puzzle_types::singleton::{LauncherSolution, SingletonArgs};
5use chia_puzzle_types::{
6 LineageProof, Proof,
7 singleton::{SingletonSolution, SingletonStruct},
8};
9use chia_sdk_types::puzzles::{
10 RawActionLayerSolution, ReserveFinalizerSolution, RewardDistributorCommitmentSlotValue,
11 RewardDistributorEntrySlotValue, RewardDistributorRewardSlotValue, RewardDistributorSlotNonce,
12 SlotInfo,
13};
14use chia_sdk_types::{Condition, Conditions};
15use clvm_traits::{FromClvm, clvm_tuple, match_tuple};
16use clvm_utils::{ToTreeHash, tree_hash};
17use clvmr::NodePtr;
18
19use crate::{
20 ActionLayer, ActionLayerSolution, ActionSingleton, Cat, CatSpend, DriverError, Layer, Puzzle,
21 RewardDistributorActionLog, RewardDistributorAddEntryAction,
22 RewardDistributorAddIncentivesAction, RewardDistributorCommitIncentivesAction,
23 RewardDistributorInitiatePayoutAction, RewardDistributorNewEpochAction,
24 RewardDistributorRefreshAction, RewardDistributorRemoveEntryAction,
25 RewardDistributorStakeAction, RewardDistributorStateTransition, RewardDistributorSyncAction,
26 RewardDistributorType, RewardDistributorUnstakeAction,
27 RewardDistributorWithdrawIncentivesAction, SingletonAction, SingletonLayer, Slot, Spend,
28 SpendContext,
29};
30
31use super::{Reserve, RewardDistributorConstants, RewardDistributorInfo, RewardDistributorState};
32
33#[derive(Debug, Clone)]
34pub struct RewardDistributorPendingSpendInfo {
35 pub actions: Vec<Spend>,
36
37 pub spent_reward_slots: Vec<RewardDistributorRewardSlotValue>,
38 pub spent_commitment_slots: Vec<RewardDistributorCommitmentSlotValue>,
39 pub spent_entry_slots: Vec<RewardDistributorEntrySlotValue>,
40
41 pub created_reward_slots: Vec<RewardDistributorRewardSlotValue>,
42 pub created_commitment_slots: Vec<RewardDistributorCommitmentSlotValue>,
43 pub created_entry_slots: Vec<RewardDistributorEntrySlotValue>,
44
45 pub logs: Vec<RewardDistributorActionLog>,
46
47 pub latest_state: (NodePtr, RewardDistributorState),
48
49 pub signature: Signature,
50 pub other_cats: Vec<CatSpend>,
51}
52
53impl RewardDistributorPendingSpendInfo {
54 pub fn new(latest_state: RewardDistributorState) -> Self {
55 Self {
56 actions: vec![],
57 created_reward_slots: vec![],
58 created_commitment_slots: vec![],
59 created_entry_slots: vec![],
60 spent_reward_slots: vec![],
61 spent_commitment_slots: vec![],
62 spent_entry_slots: vec![],
63 logs: vec![],
64 latest_state: (NodePtr::NIL, latest_state),
65 signature: Signature::default(),
66 other_cats: vec![],
67 }
68 }
69
70 pub fn add_delta(&mut self, delta: RewardDistributorPendingSpendInfo) {
71 self.actions.extend(delta.actions);
72
73 self.spent_reward_slots.extend(delta.spent_reward_slots);
74 self.spent_commitment_slots
75 .extend(delta.spent_commitment_slots);
76 self.spent_entry_slots.extend(delta.spent_entry_slots);
77
78 self.created_reward_slots.extend(delta.created_reward_slots);
79 self.created_commitment_slots
80 .extend(delta.created_commitment_slots);
81 self.created_entry_slots.extend(delta.created_entry_slots);
82
83 self.logs.extend(delta.logs);
84
85 self.latest_state = delta.latest_state;
86
87 }
90}
91
92#[derive(Debug, Clone)]
93#[must_use]
94pub struct RewardDistributor {
95 pub coin: Coin,
96 pub proof: Proof,
97 pub info: RewardDistributorInfo,
98 pub reserve: Reserve,
99
100 pub pending_spend: RewardDistributorPendingSpendInfo,
101}
102
103impl RewardDistributor {
104 pub fn new(coin: Coin, proof: Proof, info: RewardDistributorInfo, reserve: Reserve) -> Self {
105 Self {
106 coin,
107 proof,
108 info,
109 reserve,
110 pending_spend: RewardDistributorPendingSpendInfo::new(info.state),
111 }
112 }
113}
114
115impl RewardDistributor {
116 #[allow(clippy::type_complexity)]
117 pub fn pending_info_delta_from_spend(
118 ctx: &mut SpendContext,
119 action_spend: Spend,
120 current_state_and_ephemeral: (NodePtr, RewardDistributorState),
121 constants: RewardDistributorConstants,
122 ) -> Result<RewardDistributorPendingSpendInfo, DriverError> {
123 let mut spent_reward_slots = vec![];
124 let mut spent_commitment_slots = vec![];
125 let mut spent_entry_slots = vec![];
126
127 let mut created_reward_slots = vec![];
128 let mut created_commitment_slots = vec![];
129 let mut created_entry_slots = vec![];
130
131 let new_epoch_action = RewardDistributorNewEpochAction::from_constants(&constants);
132 let new_epoch_hash = new_epoch_action.tree_hash();
133
134 let commit_incentives_action =
135 RewardDistributorCommitIncentivesAction::from_constants(&constants);
136 let commit_incentives_hash = commit_incentives_action.tree_hash();
137
138 let add_entry_action = RewardDistributorAddEntryAction::from_constants(&constants);
139 let add_entry_hash = add_entry_action.tree_hash();
140
141 let remove_entry_action = RewardDistributorRemoveEntryAction::from_constants(&constants);
142 let remove_entry_hash = remove_entry_action.tree_hash();
143
144 let stake_action = RewardDistributorStakeAction::from_constants(&constants);
145 let stake_hash = stake_action.tree_hash();
146
147 let unstake_action = RewardDistributorUnstakeAction::from_constants(&constants);
148 let unstake_hash = unstake_action.tree_hash();
149
150 let withdraw_incentives_action =
151 RewardDistributorWithdrawIncentivesAction::from_constants(&constants);
152 let withdraw_incentives_hash = withdraw_incentives_action.tree_hash();
153
154 let initiate_payout_action =
155 RewardDistributorInitiatePayoutAction::from_constants(&constants);
156 let initiate_payout_hash = initiate_payout_action.tree_hash();
157
158 let add_incentives_action =
159 RewardDistributorAddIncentivesAction::from_constants(&constants);
160 let add_incentives_hash = add_incentives_action.tree_hash();
161
162 let sync_action = RewardDistributorSyncAction::from_constants(&constants);
163 let sync_hash = sync_action.tree_hash();
164
165 let refresh_action = RewardDistributorRefreshAction::from_constants(&constants);
166 let refresh_hash = refresh_action.tree_hash();
167
168 let actual_solution = ctx.alloc(&clvm_tuple!(
169 current_state_and_ephemeral,
170 action_spend.solution
171 ))?;
172
173 let output = ctx.run(action_spend.puzzle, actual_solution)?;
174 let (new_state_and_ephemeral, _) =
175 ctx.extract::<match_tuple!((NodePtr, RewardDistributorState), NodePtr)>(output)?;
176
177 let changes = RewardDistributorStateTransition {
178 old_state: current_state_and_ephemeral.1,
179 new_state: new_state_and_ephemeral.1,
180 };
181
182 let raw_action_hash = ctx.tree_hash(action_spend.puzzle);
183
184 let log = if raw_action_hash == new_epoch_hash {
185 RewardDistributorActionLog::NewEpoch(RewardDistributorNewEpochAction::get_log(
186 ctx,
187 action_spend.solution,
188 changes,
189 )?)
190 } else if raw_action_hash == commit_incentives_hash {
191 RewardDistributorActionLog::CommitIncentives(
192 RewardDistributorCommitIncentivesAction::get_log(
193 ctx,
194 action_spend.solution,
195 changes,
196 constants.epoch_seconds,
197 )?,
198 )
199 } else if raw_action_hash == add_entry_hash {
200 RewardDistributorActionLog::AddEntry(RewardDistributorAddEntryAction::get_log(
201 ctx,
202 action_spend.solution,
203 changes,
204 )?)
205 } else if raw_action_hash == stake_hash {
206 RewardDistributorActionLog::Stake(RewardDistributorStakeAction::get_log(
207 ctx,
208 action_spend.solution,
209 changes,
210 constants.reward_distributor_type,
211 )?)
212 } else if raw_action_hash == remove_entry_hash {
213 RewardDistributorActionLog::RemoveEntry(RewardDistributorRemoveEntryAction::get_log(
214 ctx,
215 action_spend.solution,
216 changes,
217 )?)
218 } else if raw_action_hash == unstake_hash {
219 RewardDistributorActionLog::Unstake(RewardDistributorUnstakeAction::get_log(
220 ctx,
221 action_spend.solution,
222 changes,
223 constants.launcher_id,
224 constants.reward_distributor_type,
225 current_state_and_ephemeral.0,
226 )?)
227 } else if raw_action_hash == withdraw_incentives_hash {
228 RewardDistributorActionLog::WithdrawIncentives(
229 RewardDistributorWithdrawIncentivesAction::get_log(
230 ctx,
231 action_spend.solution,
232 changes,
233 constants.withdrawal_share_bps,
234 )?,
235 )
236 } else if raw_action_hash == initiate_payout_hash {
237 RewardDistributorActionLog::InitiatePayout(
238 RewardDistributorInitiatePayoutAction::get_log(
239 ctx,
240 action_spend.solution,
241 changes,
242 )?,
243 )
244 } else if raw_action_hash == refresh_hash {
245 let RewardDistributorType::CuratedNft {
246 store_launcher_id, ..
247 } = constants.reward_distributor_type
248 else {
249 return Err(DriverError::InvalidMerkleProof);
250 };
251
252 RewardDistributorActionLog::RefreshNftsFromDl(RewardDistributorRefreshAction::get_log(
253 ctx,
254 action_spend.solution,
255 changes,
256 store_launcher_id,
257 )?)
258 } else if raw_action_hash == add_incentives_hash {
259 RewardDistributorActionLog::AddIncentives(
260 RewardDistributorAddIncentivesAction::get_log(ctx, action_spend.solution, changes)?,
261 )
262 } else if raw_action_hash == sync_hash {
263 RewardDistributorActionLog::Sync(RewardDistributorSyncAction::get_log(
264 ctx,
265 action_spend.solution,
266 changes,
267 )?)
268 } else {
269 return Err(DriverError::InvalidMerkleProof);
270 };
271
272 log.extend_spent_slots(
273 &mut spent_reward_slots,
274 &mut spent_commitment_slots,
275 &mut spent_entry_slots,
276 );
277 log.extend_created_slots(
278 &mut created_reward_slots,
279 &mut created_commitment_slots,
280 &mut created_entry_slots,
281 );
282
283 Ok(RewardDistributorPendingSpendInfo {
284 actions: vec![action_spend],
285 spent_reward_slots,
286 spent_commitment_slots,
287 spent_entry_slots,
288 created_reward_slots,
289 created_commitment_slots,
290 created_entry_slots,
291 logs: vec![log],
292 latest_state: new_state_and_ephemeral,
293 signature: Signature::default(),
294 other_cats: vec![],
295 })
296 }
297
298 pub fn pending_info_from_spend(
299 ctx: &mut SpendContext,
300 inner_solution: NodePtr,
301 initial_state: RewardDistributorState,
302 constants: RewardDistributorConstants,
303 signature: Signature,
304 ) -> Result<RewardDistributorPendingSpendInfo, DriverError> {
305 let mut pending_spend_info = RewardDistributorPendingSpendInfo::new(initial_state);
306
307 let inner_solution =
308 ActionLayer::<RewardDistributorState, NodePtr>::parse_solution(ctx, inner_solution)?;
309
310 for raw_action in &inner_solution.action_spends {
311 let delta = Self::pending_info_delta_from_spend(
312 ctx,
313 *raw_action,
314 pending_spend_info.latest_state,
315 constants,
316 )?;
317
318 pending_spend_info.add_delta(delta);
319 }
320
321 pending_spend_info.signature = signature;
322 Ok(pending_spend_info)
323 }
324
325 pub fn from_spend(
326 ctx: &mut SpendContext,
327 spend: &CoinSpend,
328 reserve_lineage_proof: Option<LineageProof>,
329 constants: RewardDistributorConstants,
330 signature: Signature,
331 ) -> Result<Option<Self>, DriverError> {
332 let coin = spend.coin;
333 if coin.amount != 1 {
334 return Ok(None);
335 }
336
337 let puzzle_ptr = ctx.alloc(&spend.puzzle_reveal)?;
338 let puzzle = Puzzle::parse(ctx, puzzle_ptr);
339 let solution_ptr = ctx.alloc(&spend.solution)?;
340
341 let Some(info) = RewardDistributorInfo::parse(ctx, puzzle, constants)? else {
342 return Ok(None);
343 };
344
345 let solution = ctx.extract::<SingletonSolution<NodePtr>>(solution_ptr)?;
346 let proof = solution.lineage_proof;
347
348 let pending_spend = Self::pending_info_from_spend(
349 ctx,
350 solution.inner_solution,
351 info.state,
352 constants,
353 signature,
354 )?;
355
356 let inner_solution =
357 RawActionLayerSolution::<NodePtr, NodePtr, ReserveFinalizerSolution>::from_clvm(
358 ctx,
359 solution.inner_solution,
360 )?;
361
362 let reserve = Reserve::new(
363 inner_solution.finalizer_solution.reserve_parent_id,
364 reserve_lineage_proof.unwrap_or(LineageProof {
365 parent_parent_coin_info: Bytes32::default(),
366 parent_inner_puzzle_hash: Bytes32::default(),
367 parent_amount: 0,
368 }), constants.reserve_asset_id,
370 SingletonStruct::new(info.constants.launcher_id)
371 .tree_hash()
372 .into(),
373 0,
374 info.state.total_reserves,
375 );
376
377 Ok(Some(RewardDistributor {
378 coin,
379 proof,
380 info,
381 reserve,
382 pending_spend,
383 }))
384 }
385
386 pub fn child_lineage_proof(&self) -> LineageProof {
387 LineageProof {
388 parent_parent_coin_info: self.coin.parent_coin_info,
389 parent_inner_puzzle_hash: self.info.inner_puzzle_hash().into(),
390 parent_amount: self.coin.amount,
391 }
392 }
393
394 pub fn from_parent_spend(
395 ctx: &mut SpendContext,
396 parent_spend: &CoinSpend,
397 constants: RewardDistributorConstants,
398 ) -> Result<Option<Self>, DriverError>
399 where
400 Self: Sized,
401 {
402 let Some(parent_registry) =
403 Self::from_spend(ctx, parent_spend, None, constants, Signature::default())?
404 else {
405 return Ok(None);
406 };
407
408 let new_info = parent_registry
409 .info
410 .with_state(parent_registry.pending_spend.latest_state.1);
411
412 Ok(Some(RewardDistributor {
413 coin: Coin::new(
414 parent_registry.coin.coin_id(),
415 new_info.puzzle_hash().into(),
416 1,
417 ),
418 proof: Proof::Lineage(parent_registry.child_lineage_proof()),
419 info: new_info,
420 reserve: parent_registry.reserve.child(new_info.state.total_reserves),
421 pending_spend: RewardDistributorPendingSpendInfo::new(new_info.state),
422 }))
423 }
424
425 pub fn child(&self, child_state: RewardDistributorState) -> Self {
426 let new_info = self.info.with_state(child_state);
427 let new_coin = Coin::new(self.coin.coin_id(), new_info.puzzle_hash().into(), 1);
428 let new_reserve = self.reserve.child(child_state.total_reserves);
429
430 RewardDistributor {
431 coin: new_coin,
432 proof: Proof::Lineage(self.child_lineage_proof()),
433 info: new_info,
434 reserve: new_reserve,
435 pending_spend: RewardDistributorPendingSpendInfo::new(new_info.state),
436 }
437 }
438
439 #[allow(clippy::type_complexity)]
440 pub fn from_launcher_solution(
441 ctx: &mut SpendContext,
442 launcher_coin: Coin,
443 launcher_solution: NodePtr,
444 ) -> Result<Option<(RewardDistributorConstants, RewardDistributorState, Coin)>, DriverError>
445 where
446 Self: Sized,
447 {
448 let Ok(launcher_solution) =
449 ctx.extract::<LauncherSolution<(u64, RewardDistributorConstants)>>(launcher_solution)
450 else {
451 return Ok(None);
452 };
453
454 let launcher_id = launcher_coin.coin_id();
455 let (first_epoch_start, constants) = launcher_solution.key_value_list;
456
457 if constants != constants.with_launcher_id(launcher_id) {
458 return Err(DriverError::Custom(
459 "Distributor constants invalid".to_string(),
460 ));
461 }
462
463 let distributor_eve_coin =
464 Coin::new(launcher_id, launcher_solution.singleton_puzzle_hash, 1);
465
466 let initial_state = RewardDistributorState::initial(first_epoch_start);
467
468 Ok(Some((constants, initial_state, distributor_eve_coin)))
469 }
470
471 #[allow(clippy::type_complexity)]
472 pub fn from_eve_coin_spend(
473 ctx: &mut SpendContext,
474 constants: RewardDistributorConstants,
475 initial_state: RewardDistributorState,
476 eve_coin_spend: &CoinSpend,
477 reserve_parent_id: Bytes32,
478 reserve_lineage_proof: LineageProof,
479 ) -> Result<Option<(RewardDistributor, Slot<RewardDistributorRewardSlotValue>)>, DriverError>
480 where
481 Self: Sized,
482 {
483 let eve_coin_puzzle_ptr = ctx.alloc(&eve_coin_spend.puzzle_reveal)?;
484 let eve_coin_puzzle = Puzzle::parse(ctx, eve_coin_puzzle_ptr);
485 let Some(eve_coin_puzzle) = SingletonLayer::<NodePtr>::parse_puzzle(ctx, eve_coin_puzzle)?
486 else {
487 return Err(DriverError::Custom("Eve coin not a singleton".to_string()));
488 };
489
490 let eve_coin_inner_puzzle_hash = tree_hash(ctx, eve_coin_puzzle.inner_puzzle);
491
492 let eve_coin_solution_ptr = ctx.alloc(&eve_coin_spend.solution)?;
493 let eve_coin_output = ctx.run(eve_coin_puzzle_ptr, eve_coin_solution_ptr)?;
494 let eve_coin_output = ctx.extract::<Conditions<NodePtr>>(eve_coin_output)?;
495
496 let Some(Condition::CreateCoin(odd_create_coin)) = eve_coin_output.into_iter().find(|c| {
497 if let Condition::CreateCoin(create_coin) = c {
498 create_coin.amount % 2 == 1
500 } else {
501 false
502 }
503 }) else {
504 return Err(DriverError::Custom(
505 "Eve coin did not create a coin".to_string(),
506 ));
507 };
508
509 let new_coin = Coin::new(
510 eve_coin_spend.coin.coin_id(),
511 odd_create_coin.puzzle_hash,
512 odd_create_coin.amount,
513 );
514 let lineage_proof = LineageProof {
515 parent_parent_coin_info: eve_coin_spend.coin.parent_coin_info,
516 parent_inner_puzzle_hash: eve_coin_inner_puzzle_hash.into(),
517 parent_amount: eve_coin_spend.coin.amount,
518 };
519 let reserve = Reserve::new(
520 reserve_parent_id,
521 reserve_lineage_proof,
522 constants.reserve_asset_id,
523 SingletonStruct::new(constants.launcher_id)
524 .tree_hash()
525 .into(),
526 0,
527 0,
528 );
529 let new_distributor = RewardDistributor::new(
530 new_coin,
531 Proof::Lineage(lineage_proof),
532 RewardDistributorInfo::new(initial_state, constants),
533 reserve,
534 );
535
536 if SingletonArgs::curry_tree_hash(
537 constants.launcher_id,
538 new_distributor.info.inner_puzzle_hash(),
539 ) != new_distributor.coin.puzzle_hash.into()
540 {
541 return Err(DriverError::Custom(
542 "Distributor singleton puzzle hash mismatch".to_string(),
543 ));
544 }
545
546 let slot_value = RewardDistributorRewardSlotValue {
547 counter: 0,
548 epoch_start: initial_state.round_time_info.epoch_end,
549 next_epoch_initialized: false,
550 rewards: 0,
551 };
552
553 let slot = Slot::new(
554 lineage_proof,
555 SlotInfo::from_value(
556 constants.launcher_id,
557 RewardDistributorSlotNonce::REWARD.to_u64(),
558 slot_value,
559 ),
560 );
561
562 Ok(Some((new_distributor, slot)))
563 }
564
565 pub fn set_pending_signature(&mut self, signature: Signature) {
566 self.pending_spend.signature = signature;
567 }
568
569 pub fn set_pending_other_cats(&mut self, other_cats: Vec<CatSpend>) {
570 self.pending_spend.other_cats = other_cats;
571 }
572
573 pub fn from_mempool_item(
574 ctx: &mut SpendContext,
575 mempool_item: SpendBundle,
576 constants: RewardDistributorConstants,
577 ) -> Result<Option<Self>, DriverError> {
578 let mut registry = None;
579
580 let mut other_cats = vec![];
581
582 for spend in &mempool_item.coin_spends {
583 if registry.is_none()
584 && let Some(parsed_registry) =
585 Self::from_spend(ctx, spend, None, constants, Signature::default())?
586 {
587 registry = Some(parsed_registry);
588 } else {
589 let puzzle_ptr = ctx.alloc(&spend.puzzle_reveal)?;
592 let puzzle = Puzzle::parse(ctx, puzzle_ptr);
593 let solution_ptr = ctx.alloc(&spend.solution)?;
594
595 if let Ok(Some(parsed_cat)) = Cat::parse(ctx, spend.coin, puzzle, solution_ptr)
596 && parsed_cat.cat.info.asset_id == constants.reserve_asset_id
597 {
598 other_cats.push(CatSpend::new(
599 parsed_cat.cat,
600 Spend::new(parsed_cat.p2_puzzle.ptr(), parsed_cat.p2_solution),
601 ));
602 }
603 }
604 }
605
606 let Some(registry) = registry else {
607 return Ok(None);
608 };
609
610 let reserve_coin = Coin::new(
615 registry.reserve.coin.parent_coin_info,
616 registry.info.constants.reserve_full_puzzle_hash,
617 registry.reserve.coin.amount,
618 );
619 let Some(reserve_spend) = mempool_item
620 .coin_spends
621 .iter()
622 .find(|c| c.coin == reserve_coin)
623 else {
624 return Err(DriverError::Custom(
625 "Reserve spend not found in mempool item".to_string(),
626 ));
627 };
628
629 let reserve_sol_ptr = ctx.alloc(&reserve_spend.solution)?;
630 let reserve_solution = ctx.extract::<CatSolution<NodePtr>>(reserve_sol_ptr)?;
631
632 let Some(mut registry) = RewardDistributor::from_spend(
636 ctx,
637 mempool_item
638 .coin_spends
639 .iter()
640 .find(|c| c.coin == registry.coin)
641 .ok_or(DriverError::Custom(
642 "Couldn't find distributor spend in mempool item".to_string(),
643 ))?,
644 reserve_solution.lineage_proof,
645 constants,
646 Signature::default(),
647 )?
648 else {
649 return Err(DriverError::Custom(
650 "Couldn't rebuild distributor from spend a second time - something's pretty off"
651 .to_string(),
652 ));
653 };
654
655 while let Some(registry_spend) = mempool_item
656 .coin_spends
657 .iter()
658 .find(|c| c.coin.amount != 0 && c.coin.parent_coin_info == registry.coin.coin_id())
659 {
660 let Some(new_registry) = Self::from_spend(
661 ctx,
662 registry_spend,
663 Some(registry.reserve.child_lineage_proof()),
664 registry.info.constants,
665 Signature::default(),
666 )?
667 else {
668 break;
669 };
670
671 registry = new_registry;
672 }
673
674 for spend in mempool_item.coin_spends {
676 if spend.coin == registry.coin || other_cats.iter().any(|c| c.cat.coin == spend.coin) {
677 continue;
678 }
679
680 ctx.insert(spend);
681 }
682
683 other_cats.retain(|c| c.cat.coin != registry.reserve.coin);
686
687 registry.set_pending_other_cats(other_cats);
688 registry.set_pending_signature(mempool_item.aggregated_signature);
689 Ok(Some(registry))
690 }
691}
692
693impl ActionSingleton for RewardDistributor {
694 type State = RewardDistributorState;
695 type Constants = RewardDistributorConstants;
696}
697
698impl RewardDistributor {
699 pub fn finish_spend(
700 self,
701 ctx: &mut SpendContext,
702 other_cat_spends: Vec<CatSpend>,
703 ) -> Result<(Self, Signature), DriverError> {
704 let layers = self.info.into_layers(ctx)?;
705
706 let puzzle = layers.construct_puzzle(ctx)?;
707
708 let action_puzzle_hashes = self
709 .pending_spend
710 .actions
711 .iter()
712 .map(|a| ctx.tree_hash(a.puzzle).into())
713 .collect::<Vec<Bytes32>>();
714
715 let finalizer_solution = ctx.alloc(&ReserveFinalizerSolution {
716 reserve_parent_id: self.reserve.coin.parent_coin_info,
717 })?;
718
719 let child = self.child(self.pending_spend.latest_state.1);
720 let solution = layers.construct_solution(
721 ctx,
722 SingletonSolution {
723 lineage_proof: self.proof,
724 amount: self.coin.amount,
725 inner_solution: ActionLayerSolution {
726 proofs: layers
727 .inner_puzzle
728 .get_proofs(
729 &RewardDistributorInfo::action_puzzle_hashes(&self.info.constants),
730 &action_puzzle_hashes,
731 )
732 .ok_or(DriverError::Custom(
733 "Couldn't build proofs for one or more actions".to_string(),
734 ))?,
735 action_spends: self.pending_spend.actions,
736 finalizer_solution,
737 },
738 },
739 )?;
740
741 let my_spend = Spend::new(puzzle, solution);
742 ctx.spend(self.coin, my_spend)?;
743
744 let cat_spend = self.reserve.cat_spend_for_reserve_finalizer_controller(
745 ctx,
746 self.info.state,
747 self.info.inner_puzzle_hash().into(),
748 solution,
749 )?;
750
751 let mut cat_spends = other_cat_spends;
752 cat_spends.push(cat_spend);
753 cat_spends.extend(self.pending_spend.other_cats);
754 Cat::spend_all(ctx, &cat_spends)?;
755
756 Ok((child, self.pending_spend.signature))
757 }
758
759 pub fn new_action<A>(&self) -> A
760 where
761 A: SingletonAction<Self>,
762 {
763 A::from_constants(&self.info.constants)
764 }
765
766 pub fn created_slot_value_to_slot<SlotValue>(
767 &self,
768 slot_value: SlotValue,
769 nonce: RewardDistributorSlotNonce,
770 ) -> Slot<SlotValue>
771 where
772 SlotValue: Copy + ToTreeHash,
773 {
774 Slot::new(
775 LineageProof {
776 parent_parent_coin_info: self.coin.parent_coin_info,
777 parent_inner_puzzle_hash: self.info.inner_puzzle_hash().into(),
778 parent_amount: self.coin.amount,
779 },
780 SlotInfo::from_value(self.info.constants.launcher_id, nonce.to_u64(), slot_value),
781 )
782 }
783
784 pub fn insert_action_spend(
785 &mut self,
786 ctx: &mut SpendContext,
787 action_spend: Spend,
788 ) -> Result<(), DriverError> {
789 let delta = Self::pending_info_delta_from_spend(
790 ctx,
791 action_spend,
792 self.pending_spend.latest_state,
793 self.info.constants,
794 )?;
795
796 self.pending_spend.add_delta(delta);
797
798 Ok(())
799 }
800
801 pub fn actual_reward_slot_value(
802 &self,
803 slot: Slot<RewardDistributorRewardSlotValue>,
804 ) -> Slot<RewardDistributorRewardSlotValue> {
805 let mut slot = slot;
806
807 for slot_value in &self.pending_spend.created_reward_slots {
808 if slot_value.epoch_start == slot.info.value.epoch_start {
809 slot = self
810 .created_slot_value_to_slot(*slot_value, RewardDistributorSlotNonce::REWARD);
811 }
812 }
813
814 slot
815 }
816
817 pub fn actual_entry_slot_value(
818 &self,
819 slot: Slot<RewardDistributorEntrySlotValue>,
820 ) -> Slot<RewardDistributorEntrySlotValue> {
821 let mut slot = slot;
822
823 for slot_value in &self.pending_spend.created_entry_slots {
824 if slot_value.payout_puzzle_hash == slot.info.value.payout_puzzle_hash {
825 slot =
826 self.created_slot_value_to_slot(*slot_value, RewardDistributorSlotNonce::ENTRY);
827 }
828 }
829
830 slot
831 }
832
833 pub fn actual_commitment_slot_value(
834 &self,
835 slot: Slot<RewardDistributorCommitmentSlotValue>,
836 ) -> Slot<RewardDistributorCommitmentSlotValue> {
837 let mut slot = slot;
838
839 for slot_value in &self.pending_spend.created_commitment_slots {
840 if slot_value.epoch_start == slot.info.value.epoch_start {
841 slot = self.created_slot_value_to_slot(
842 *slot_value,
843 RewardDistributorSlotNonce::COMMITMENT,
844 );
845 }
846 }
847
848 slot
849 }
850}