chia_sdk_driver/primitives/option/
option_launcher.rs

1use chia_protocol::Bytes32;
2use chia_puzzle_types::{EveProof, Proof};
3use chia_sdk_types::{conditions::CreateCoin, Conditions};
4use clvm_traits::clvm_quote;
5use clvm_utils::ToTreeHash;
6use clvmr::NodePtr;
7
8use crate::{DriverError, Launcher, Spend, SpendContext};
9
10use super::{OptionContract, OptionInfo, OptionMetadata, OptionType, OptionUnderlying};
11
12#[derive(Debug, Clone, Copy)]
13pub struct UnspecifiedOption {
14    owner_puzzle_hash: Bytes32,
15    underlying: OptionUnderlying,
16    metadata: OptionMetadata,
17}
18
19#[derive(Debug, Clone, Copy)]
20pub struct ReadyOption {
21    info: OptionInfo,
22    metadata: OptionMetadata,
23}
24
25#[derive(Debug, Clone)]
26pub struct OptionLauncher<S = UnspecifiedOption> {
27    launcher: Launcher,
28    state: S,
29}
30
31impl<S> OptionLauncher<S> {
32    pub fn launcher(&self) -> &Launcher {
33        &self.launcher
34    }
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
38pub struct OptionLauncherInfo {
39    pub creator_puzzle_hash: Bytes32,
40    pub owner_puzzle_hash: Bytes32,
41    pub seconds: u64,
42    pub underlying_amount: u64,
43    pub strike_type: OptionType,
44}
45
46impl OptionLauncherInfo {
47    pub fn new(
48        creator_puzzle_hash: Bytes32,
49        owner_puzzle_hash: Bytes32,
50        seconds: u64,
51        underlying_amount: u64,
52        strike_type: OptionType,
53    ) -> Self {
54        Self {
55            creator_puzzle_hash,
56            owner_puzzle_hash,
57            seconds,
58            underlying_amount,
59            strike_type,
60        }
61    }
62}
63
64impl OptionLauncher<UnspecifiedOption> {
65    pub fn new(
66        ctx: &mut SpendContext,
67        parent_coin_id: Bytes32,
68        info: OptionLauncherInfo,
69        singleton_amount: u64,
70    ) -> Result<Self, DriverError> {
71        Self::with_amount(ctx, parent_coin_id, 1, info, singleton_amount)
72    }
73
74    pub fn with_amount(
75        ctx: &mut SpendContext,
76        parent_coin_id: Bytes32,
77        launcher_amount: u64,
78        info: OptionLauncherInfo,
79        singleton_amount: u64,
80    ) -> Result<Self, DriverError> {
81        let memos = ctx.hint(info.creator_puzzle_hash)?;
82        let launcher = Launcher::with_memos(parent_coin_id, launcher_amount, memos)
83            .with_singleton_amount(singleton_amount);
84        let launcher_id = launcher.coin().coin_id();
85
86        Ok(Self {
87            launcher,
88            state: UnspecifiedOption {
89                owner_puzzle_hash: info.owner_puzzle_hash,
90                underlying: OptionUnderlying::new(
91                    launcher_id,
92                    info.creator_puzzle_hash,
93                    info.seconds,
94                    info.underlying_amount,
95                    info.strike_type,
96                ),
97                metadata: OptionMetadata::new(info.seconds, info.strike_type),
98            },
99        })
100    }
101
102    pub fn create_early(
103        ctx: &mut SpendContext,
104        parent_coin_id: Bytes32,
105        launcher_amount: u64,
106        info: OptionLauncherInfo,
107        singleton_amount: u64,
108    ) -> Result<(CreateCoin<NodePtr>, Self), DriverError> {
109        let memos = ctx.hint(info.creator_puzzle_hash)?;
110        let (create_coin, launcher) =
111            Launcher::create_early_with_memos(parent_coin_id, launcher_amount, memos);
112        let launcher = launcher.with_singleton_amount(singleton_amount);
113        let launcher_id = launcher.coin().coin_id();
114
115        let launcher = Self {
116            launcher,
117            state: UnspecifiedOption {
118                owner_puzzle_hash: info.owner_puzzle_hash,
119                underlying: OptionUnderlying::new(
120                    launcher_id,
121                    info.creator_puzzle_hash,
122                    info.seconds,
123                    info.underlying_amount,
124                    info.strike_type,
125                ),
126                metadata: OptionMetadata::new(info.seconds, info.strike_type),
127            },
128        };
129
130        Ok((create_coin, launcher))
131    }
132
133    pub fn underlying(&self) -> OptionUnderlying {
134        self.state.underlying
135    }
136
137    pub fn p2_puzzle_hash(&self) -> Bytes32 {
138        self.state.underlying.tree_hash().into()
139    }
140
141    pub fn metadata(&self) -> OptionMetadata {
142        self.state.metadata
143    }
144
145    pub fn with_underlying(self, underlying_coin_id: Bytes32) -> OptionLauncher<ReadyOption> {
146        let launcher_id = self.launcher.coin().coin_id();
147
148        let info = OptionInfo::new(
149            launcher_id,
150            underlying_coin_id,
151            self.state.underlying.delegated_puzzle().tree_hash().into(),
152            self.state.owner_puzzle_hash,
153        );
154
155        OptionLauncher {
156            launcher: self.launcher,
157            state: ReadyOption {
158                info,
159                metadata: self.state.metadata,
160            },
161        }
162    }
163}
164
165impl OptionLauncher<ReadyOption> {
166    pub fn info(&self) -> OptionInfo {
167        self.state.info
168    }
169
170    pub fn mint(self, ctx: &mut SpendContext) -> Result<(Conditions, OptionContract), DriverError> {
171        let owner_puzzle_hash = self.state.info.p2_puzzle_hash;
172
173        let memos = ctx.hint(owner_puzzle_hash)?;
174        let singleton_amount = self.launcher.singleton_amount();
175        let conditions = Conditions::new().create_coin(owner_puzzle_hash, singleton_amount, memos);
176
177        let inner_puzzle = ctx.alloc(&clvm_quote!(conditions))?;
178        let eve_p2_puzzle_hash = ctx.tree_hash(inner_puzzle).into();
179        let inner_spend = Spend::new(inner_puzzle, NodePtr::NIL);
180
181        let (mint_eve_option, eve_option) = self.mint_eve(ctx, eve_p2_puzzle_hash)?;
182        eve_option.spend(ctx, inner_spend)?;
183
184        let child = eve_option.child(owner_puzzle_hash, singleton_amount);
185
186        Ok((mint_eve_option, child))
187    }
188
189    pub fn mint_eve(
190        self,
191        ctx: &mut SpendContext,
192        p2_puzzle_hash: Bytes32,
193    ) -> Result<(Conditions, OptionContract), DriverError> {
194        let launcher_coin = self.launcher.coin();
195
196        let info = self.state.info.with_p2_puzzle_hash(p2_puzzle_hash);
197
198        let (launch_singleton, eve_coin) =
199            self.launcher
200                .spend(ctx, info.inner_puzzle_hash().into(), self.state.metadata)?;
201
202        let proof = Proof::Eve(EveProof {
203            parent_parent_coin_info: launcher_coin.parent_coin_info,
204            parent_amount: launcher_coin.amount,
205        });
206
207        Ok((launch_singleton, OptionContract::new(eve_coin, proof, info)))
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use chia_protocol::Coin;
214    use chia_puzzle_types::Memos;
215    use chia_sdk_test::Simulator;
216
217    use crate::StandardLayer;
218
219    use super::*;
220
221    #[test]
222    fn test_mint_option() -> anyhow::Result<()> {
223        let mut sim = Simulator::new();
224        let ctx = &mut SpendContext::new();
225
226        let alice = sim.bls(1);
227        let alice_p2 = StandardLayer::new(alice.pk);
228
229        let parent_coin = sim.new_coin(alice.puzzle_hash, 1);
230
231        let launcher = OptionLauncher::new(
232            ctx,
233            alice.coin.coin_id(),
234            OptionLauncherInfo::new(
235                alice.puzzle_hash,
236                alice.puzzle_hash,
237                10,
238                1,
239                OptionType::Xch { amount: 1 },
240            ),
241            1,
242        )?;
243        let p2_option = launcher.p2_puzzle_hash();
244
245        alice_p2.spend(
246            ctx,
247            parent_coin,
248            Conditions::new().create_coin(p2_option, 1, Memos::None),
249        )?;
250        let launcher =
251            launcher.with_underlying(Coin::new(parent_coin.coin_id(), p2_option, 1).coin_id());
252
253        let (mint_option, _option) = launcher.mint(ctx)?;
254        alice_p2.spend(ctx, alice.coin, mint_option)?;
255
256        sim.spend_coins(ctx.take(), &[alice.sk.clone()])?;
257
258        Ok(())
259    }
260}