chia_sdk_driver/layers/action_layer/actions/reward_distributor/
stake.rs1use chia_protocol::Bytes32;
2use chia_puzzle_types::{
3 nft::NftRoyaltyTransferPuzzleArgs,
4 offer::{NotarizedPayment, Payment},
5 singleton::SingletonStruct,
6};
7use chia_puzzles::{NFT_OWNERSHIP_LAYER_HASH, NFT_STATE_LAYER_HASH, SETTLEMENT_PAYMENT_HASH};
8use chia_sdk_types::{
9 Conditions, Mod, announcement_id,
10 puzzles::{
11 NONCE_WRAPPER_PUZZLE_HASH, NftLauncherProof, NonceWrapperArgs,
12 P2DelegatedBySingletonLayerArgs, RewardDistributorEntrySlotValue,
13 RewardDistributorSlotNonce, RewardDistributorStakeActionArgs,
14 RewardDistributorStakeActionSolution,
15 },
16};
17use clvm_traits::clvm_tuple;
18use clvm_utils::{CurriedProgram, ToTreeHash, TreeHash};
19use clvmr::NodePtr;
20
21use crate::{
22 Asset, DriverError, HashedPtr, Nft, RewardDistributor, RewardDistributorConstants,
23 RewardDistributorState, SingletonAction, Slot, Spend, SpendContext,
24};
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub struct RewardDistributorStakeAction {
28 pub launcher_id: Bytes32,
29 pub did_launcher_id: Bytes32,
30 pub max_second_offset: u64,
31}
32
33impl ToTreeHash for RewardDistributorStakeAction {
34 fn tree_hash(&self) -> TreeHash {
35 Self::new_args(
36 self.launcher_id,
37 self.did_launcher_id,
38 self.max_second_offset,
39 )
40 .curry_tree_hash()
41 }
42}
43
44impl SingletonAction<RewardDistributor> for RewardDistributorStakeAction {
45 fn from_constants(constants: &RewardDistributorConstants) -> Self {
46 Self {
47 launcher_id: constants.launcher_id,
48 did_launcher_id: constants.manager_or_collection_did_launcher_id,
49 max_second_offset: constants.max_seconds_offset,
50 }
51 }
52}
53
54impl RewardDistributorStakeAction {
55 pub fn new_args(
56 launcher_id: Bytes32,
57 did_launcher_id: Bytes32,
58 max_second_offset: u64,
59 ) -> RewardDistributorStakeActionArgs {
60 RewardDistributorStakeActionArgs {
61 did_singleton_struct: SingletonStruct::new(did_launcher_id),
62 nft_state_layer_mod_hash: NFT_STATE_LAYER_HASH.into(),
63 nft_ownership_layer_mod_hash: NFT_OWNERSHIP_LAYER_HASH.into(),
64 offer_mod_hash: SETTLEMENT_PAYMENT_HASH.into(),
65 nonce_mod_hash: NONCE_WRAPPER_PUZZLE_HASH.into(),
66 my_p2_puzzle_hash: Self::my_p2_puzzle_hash(launcher_id),
67 entry_slot_1st_curry_hash: Slot::<()>::first_curry_hash(
68 launcher_id,
69 RewardDistributorSlotNonce::ENTRY.to_u64(),
70 )
71 .into(),
72 max_second_offset,
73 }
74 }
75
76 pub fn my_p2_puzzle_hash(launcher_id: Bytes32) -> Bytes32 {
77 P2DelegatedBySingletonLayerArgs::curry_tree_hash(
78 SingletonStruct::new(launcher_id).tree_hash().into(),
79 1,
80 )
81 .into()
82 }
83
84 fn construct_puzzle(&self, ctx: &mut SpendContext) -> Result<NodePtr, DriverError> {
85 ctx.curry(Self::new_args(
86 self.launcher_id,
87 self.did_launcher_id,
88 self.max_second_offset,
89 ))
90 }
91
92 pub fn created_slot_value(
93 ctx: &SpendContext,
94 state: &RewardDistributorState,
95 solution: NodePtr,
96 ) -> Result<RewardDistributorEntrySlotValue, DriverError> {
97 let solution = ctx.extract::<RewardDistributorStakeActionSolution>(solution)?;
98
99 Ok(RewardDistributorEntrySlotValue {
100 payout_puzzle_hash: solution.entry_custody_puzzle_hash,
101 initial_cumulative_payout: state.round_reward_info.cumulative_payout,
102 shares: 1,
103 })
104 }
105
106 pub fn spend(
107 self,
108 ctx: &mut SpendContext,
109 distributor: &mut RewardDistributor,
110 current_nft: Nft,
111 nft_launcher_proof: NftLauncherProof,
112 entry_custody_puzzle_hash: Bytes32,
113 ) -> Result<(Conditions, NotarizedPayment, Nft), DriverError> {
114 let ephemeral_counter =
115 ctx.extract::<HashedPtr>(distributor.pending_spend.latest_state.0)?;
116 let my_id = distributor.coin.coin_id();
117
118 let my_p2_treehash = Self::my_p2_puzzle_hash(self.launcher_id).into();
120 let payment_puzzle_hash: Bytes32 = CurriedProgram {
121 program: NONCE_WRAPPER_PUZZLE_HASH,
122 args: NonceWrapperArgs::<Bytes32, TreeHash> {
123 nonce: entry_custody_puzzle_hash,
124 inner_puzzle: my_p2_treehash,
125 },
126 }
127 .tree_hash()
128 .into();
129 let notarized_payment = NotarizedPayment {
130 nonce: clvm_tuple!(ephemeral_counter.tree_hash(), my_id)
131 .tree_hash()
132 .into(),
133 payments: vec![Payment::new(
134 payment_puzzle_hash,
135 1,
136 ctx.hint(payment_puzzle_hash)?,
137 )],
138 };
139
140 let nft = current_nft.child(
142 SETTLEMENT_PAYMENT_HASH.into(),
143 None,
144 current_nft.info.metadata,
145 current_nft.amount(),
146 );
147 let action_solution = ctx.alloc(&RewardDistributorStakeActionSolution {
148 my_id,
149 nft_metadata_hash: nft.info.metadata.tree_hash().into(),
150 nft_metadata_updater_hash_hash: nft
151 .info
152 .metadata_updater_puzzle_hash
153 .tree_hash()
154 .into(),
155 nft_transfer_porgram_hash: NftRoyaltyTransferPuzzleArgs::curry_tree_hash(
156 nft.info.launcher_id,
157 nft.info.royalty_puzzle_hash,
158 nft.info.royalty_basis_points,
159 )
160 .into(),
161 nft_launcher_proof,
162 entry_custody_puzzle_hash,
163 })?;
164 let action_puzzle = self.construct_puzzle(ctx)?;
165
166 let notarized_payment_ptr = ctx.alloc(¬arized_payment)?;
167 let msg: Bytes32 = ctx.tree_hash(notarized_payment_ptr).into();
168 distributor.insert_action_spend(ctx, Spend::new(action_puzzle, action_solution))?;
169
170 Ok((
171 Conditions::new().assert_puzzle_announcement(announcement_id(
172 distributor.coin.puzzle_hash,
173 announcement_id(nft.coin.puzzle_hash, msg),
174 )),
175 notarized_payment,
176 nft.child(payment_puzzle_hash, None, nft.info.metadata, nft.amount()),
177 ))
178 }
179}