chia_sdk_driver/layers/action_layer/actions/reward_distributor/
stake.rs

1use 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        // calculate notarized payment
119        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        // spend self
141        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(&notarized_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}