chia_sdk_driver/primitives/option/
option_launcher.rs1use 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}