chia_sdk_driver/primitives/option/
option_underlying.rs

1use chia_protocol::{Bytes32, Coin};
2use chia_puzzle_types::{
3    cat::CatArgs,
4    offer::{NotarizedPayment, Payment},
5    Memos,
6};
7use chia_puzzles::SETTLEMENT_PAYMENT_HASH;
8use chia_sdk_types::{
9    conditions::{
10        AssertBeforeSecondsAbsolute, AssertPuzzleAnnouncement, AssertSecondsAbsolute, CreateCoin,
11    },
12    payment_assertion,
13    puzzles::{
14        AugmentedConditionArgs, AugmentedConditionSolution, P2OneOfManySolution, RevocationArgs,
15        SingletonMember, SingletonMemberSolution,
16    },
17    MerkleTree, Mod,
18};
19use clvm_traits::{clvm_list, clvm_quote, match_list, ClvmEncoder, ToClvm};
20use clvm_utils::{ToTreeHash, TreeHash, TreeHasher};
21use clvmr::NodePtr;
22
23use crate::{
24    member_puzzle_hash, DriverError, InnerPuzzleSpend, Layer, MipsSpend, P2OneOfManyLayer, Spend,
25    SpendContext,
26};
27
28use super::OptionType;
29
30pub type OptionDelegatedPuzzle = (
31    u8,
32    match_list!(
33        AssertBeforeSecondsAbsolute,
34        AssertPuzzleAnnouncement,
35        CreateCoin<Bytes32>
36    ),
37);
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
40pub struct OptionUnderlying {
41    pub launcher_id: Bytes32,
42    pub creator_puzzle_hash: Bytes32,
43    pub seconds: u64,
44    pub amount: u64,
45    pub strike_type: OptionType,
46}
47
48impl OptionUnderlying {
49    pub fn new(
50        launcher_id: Bytes32,
51        creator_puzzle_hash: Bytes32,
52        seconds: u64,
53        amount: u64,
54        strike_type: OptionType,
55    ) -> Self {
56        Self {
57            launcher_id,
58            creator_puzzle_hash,
59            seconds,
60            amount,
61            strike_type,
62        }
63    }
64
65    pub fn merkle_tree(&self) -> MerkleTree {
66        MerkleTree::new(&[self.exercise_path_hash(), self.clawback_path_hash()])
67    }
68
69    pub fn exercise_path_hash(&self) -> Bytes32 {
70        let singleton_member_hash = SingletonMember::new(self.launcher_id).curry_tree_hash();
71        member_puzzle_hash(0, Vec::new(), singleton_member_hash, true).into()
72    }
73
74    pub fn clawback_path_hash(&self) -> Bytes32 {
75        AugmentedConditionArgs::<TreeHash, TreeHash>::new(
76            AssertSecondsAbsolute::new(self.seconds).into(),
77            self.creator_puzzle_hash.into(),
78        )
79        .curry_tree_hash()
80        .into()
81    }
82
83    pub fn into_1_of_n(&self) -> P2OneOfManyLayer {
84        P2OneOfManyLayer::new(self.merkle_tree().root())
85    }
86
87    pub fn requested_payment<E>(
88        &self,
89        encoder: &mut E,
90    ) -> Result<NotarizedPayment<E::Node>, DriverError>
91    where
92        E: ClvmEncoder,
93    {
94        Ok(NotarizedPayment {
95            nonce: self.launcher_id,
96            payments: vec![Payment {
97                puzzle_hash: self.creator_puzzle_hash,
98                amount: self.strike_type.amount(),
99                memos: if self.strike_type.is_hinted() {
100                    Memos::Some(vec![self.creator_puzzle_hash].to_clvm(encoder)?)
101                } else {
102                    Memos::None
103                },
104            }],
105        })
106    }
107
108    pub fn delegated_puzzle(&self) -> OptionDelegatedPuzzle {
109        let puzzle_hash = match self.strike_type {
110            OptionType::Xch { .. } => SETTLEMENT_PAYMENT_HASH.into(),
111            OptionType::Cat { asset_id, .. } => {
112                CatArgs::curry_tree_hash(asset_id, SETTLEMENT_PAYMENT_HASH.into()).into()
113            }
114            OptionType::RevocableCat {
115                asset_id,
116                hidden_puzzle_hash,
117                ..
118            } => CatArgs::curry_tree_hash(
119                asset_id,
120                RevocationArgs::new(hidden_puzzle_hash, SETTLEMENT_PAYMENT_HASH.into())
121                    .curry_tree_hash(),
122            )
123            .into(),
124            OptionType::Nft {
125                settlement_puzzle_hash,
126                ..
127            } => settlement_puzzle_hash,
128        };
129
130        clvm_quote!(clvm_list!(
131            AssertBeforeSecondsAbsolute::new(self.seconds),
132            payment_assertion(
133                puzzle_hash,
134                self.requested_payment(&mut TreeHasher)
135                    .expect("failed to hash")
136                    .tree_hash()
137            ),
138            CreateCoin::new(SETTLEMENT_PAYMENT_HASH.into(), self.amount, Memos::None)
139        ))
140    }
141
142    pub fn exercise_spend(
143        &self,
144        ctx: &mut SpendContext,
145        singleton_inner_puzzle_hash: Bytes32,
146        singleton_amount: u64,
147    ) -> Result<Spend, DriverError> {
148        let merkle_tree = self.merkle_tree();
149
150        let custody_hash: TreeHash = self.exercise_path_hash().into();
151        let merkle_proof = merkle_tree
152            .proof(custody_hash.into())
153            .ok_or(DriverError::InvalidMerkleProof)?;
154
155        let delegated_puzzle = ctx.alloc(&self.delegated_puzzle())?;
156        let delegated_spend = Spend::new(delegated_puzzle, NodePtr::NIL);
157
158        let mut mips = MipsSpend::new(delegated_spend);
159
160        let singleton_member_puzzle = ctx.curry(SingletonMember::new(self.launcher_id))?;
161        let singleton_member_solution = ctx.alloc(&SingletonMemberSolution::new(
162            singleton_inner_puzzle_hash,
163            singleton_amount,
164        ))?;
165        mips.members.insert(
166            custody_hash,
167            InnerPuzzleSpend::new(
168                0,
169                Vec::new(),
170                Spend::new(singleton_member_puzzle, singleton_member_solution),
171            ),
172        );
173
174        let spend = mips.spend(ctx, custody_hash)?;
175
176        P2OneOfManyLayer::new(merkle_tree.root()).construct_spend(
177            ctx,
178            P2OneOfManySolution::new(merkle_proof, spend.puzzle, spend.solution),
179        )
180    }
181
182    pub fn clawback_spend(
183        &self,
184        ctx: &mut SpendContext,
185        spend: Spend,
186    ) -> Result<Spend, DriverError> {
187        let merkle_tree = self.merkle_tree();
188
189        let puzzle_hash = self.clawback_path_hash();
190        let merkle_proof = merkle_tree
191            .proof(puzzle_hash)
192            .ok_or(DriverError::InvalidMerkleProof)?;
193
194        let puzzle = ctx.curry(AugmentedConditionArgs::<NodePtr, NodePtr>::new(
195            AssertSecondsAbsolute::new(self.seconds).into(),
196            spend.puzzle,
197        ))?;
198
199        let solution = ctx.alloc(&AugmentedConditionSolution::new(spend.solution))?;
200
201        P2OneOfManyLayer::new(merkle_tree.root()).construct_spend(
202            ctx,
203            P2OneOfManySolution::new(merkle_proof, puzzle, solution),
204        )
205    }
206
207    pub fn exercise_coin_spend(
208        &self,
209        ctx: &mut SpendContext,
210        coin: Coin,
211        singleton_inner_puzzle_hash: Bytes32,
212        singleton_amount: u64,
213    ) -> Result<(), DriverError> {
214        let spend = self.exercise_spend(ctx, singleton_inner_puzzle_hash, singleton_amount)?;
215        ctx.spend(coin, spend)
216    }
217
218    pub fn clawback_coin_spend(
219        &self,
220        ctx: &mut SpendContext,
221        coin: Coin,
222        spend: Spend,
223    ) -> Result<(), DriverError> {
224        let spend = self.clawback_spend(ctx, spend)?;
225        ctx.spend(coin, spend)
226    }
227}
228
229impl ToTreeHash for OptionUnderlying {
230    fn tree_hash(&self) -> TreeHash {
231        self.into_1_of_n().tree_hash()
232    }
233}