chia_sdk_driver/primitives/
bulletin.rs1use chia_protocol::{Bytes32, Coin};
2use chia_puzzle_types::Memos;
3use chia_sdk_types::{Condition, Conditions, conditions::Remark, run_puzzle};
4use clvm_traits::{FromClvm, ToClvm};
5use clvm_utils::{ToTreeHash, TreeHash};
6use clvmr::{Allocator, NodePtr};
7
8use crate::{BulletinLayer, DriverError, HashedPtr, Layer, Puzzle, Spend, SpendContext};
9
10#[derive(Debug, Clone, PartialEq, Eq, ToClvm, FromClvm)]
11#[clvm(list)]
12pub struct BulletinMessage {
13 pub topic: String,
14 pub content: String,
15}
16
17impl BulletinMessage {
18 pub fn new(topic: String, content: String) -> Self {
19 Self { topic, content }
20 }
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct Bulletin {
25 pub coin: Coin,
26 pub hidden_puzzle_hash: Bytes32,
27 pub messages: Vec<BulletinMessage>,
28}
29
30impl Bulletin {
31 pub fn new(coin: Coin, hidden_puzzle_hash: Bytes32, messages: Vec<BulletinMessage>) -> Self {
32 Self {
33 coin,
34 hidden_puzzle_hash,
35 messages,
36 }
37 }
38
39 pub fn create(
40 parent_coin_id: Bytes32,
41 hidden_puzzle_hash: Bytes32,
42 messages: Vec<BulletinMessage>,
43 ) -> Result<(Conditions, Bulletin), DriverError> {
44 let puzzle_hash = BulletinLayer::new(TreeHash::from(hidden_puzzle_hash))
45 .tree_hash()
46 .into();
47
48 let parent_conditions = Conditions::new().create_coin(puzzle_hash, 0, Memos::None);
49
50 let bulletin = Bulletin::new(
51 Coin::new(parent_coin_id, puzzle_hash, 0),
52 hidden_puzzle_hash,
53 messages,
54 );
55
56 Ok((parent_conditions, bulletin))
57 }
58
59 pub fn conditions(&self, ctx: &mut SpendContext) -> Result<Conditions, DriverError> {
60 let mut conditions = Conditions::new();
61
62 for message in &self.messages {
63 conditions.push(Remark::new(ctx.alloc(message)?));
64 }
65
66 Ok(conditions)
67 }
68
69 pub fn spend(&self, ctx: &mut SpendContext, spend: Spend) -> Result<(), DriverError> {
70 let layer = BulletinLayer::new(spend.puzzle);
71 let coin_spend = layer.construct_coin_spend(ctx, self.coin, spend.solution)?;
72 ctx.insert(coin_spend);
73 Ok(())
74 }
75
76 pub fn parse(
77 allocator: &mut Allocator,
78 coin: Coin,
79 puzzle: Puzzle,
80 solution: NodePtr,
81 ) -> Result<Option<(Self, Puzzle, NodePtr)>, DriverError> {
82 let Some(bulletin_layer) = BulletinLayer::<HashedPtr>::parse_puzzle(allocator, puzzle)?
83 else {
84 return Ok(None);
85 };
86
87 let bulletin_solution = BulletinLayer::<NodePtr>::parse_solution(allocator, solution)?;
88
89 let output = run_puzzle(
90 allocator,
91 bulletin_layer.inner_puzzle.ptr(),
92 bulletin_solution,
93 )?;
94 let conditions = Vec::<Condition>::from_clvm(allocator, output)?;
95
96 let mut messages = Vec::new();
97
98 for condition in conditions {
99 let Some(remark) = condition.into_remark() else {
100 continue;
101 };
102
103 if let Ok(message) = BulletinMessage::from_clvm(allocator, remark.rest) {
104 messages.push(message);
105 }
106 }
107
108 let bulletin = Self::new(
109 coin,
110 bulletin_layer.inner_puzzle.tree_hash().into(),
111 messages,
112 );
113
114 Ok(Some((
115 bulletin,
116 Puzzle::parse(allocator, bulletin_layer.inner_puzzle.ptr()),
117 bulletin_solution,
118 )))
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use anyhow::Result;
125 use chia_sdk_test::Simulator;
126
127 use crate::{SpendWithConditions, StandardLayer};
128
129 use super::*;
130
131 #[test]
132 fn test_bulletin() -> Result<()> {
133 let mut sim = Simulator::new();
134 let mut ctx = SpendContext::new();
135
136 let alice = sim.bls(0);
137 let p2 = StandardLayer::new(alice.pk);
138
139 let (parent_conditions, bulletin) = Bulletin::create(
140 alice.coin.coin_id(),
141 alice.puzzle_hash,
142 vec![BulletinMessage::new(
143 "animals/rabbit/vienna-blue".to_string(),
144 "The Vienna Blue rabbit breed originally comes from Austria.".to_string(),
145 )],
146 )?;
147
148 p2.spend(&mut ctx, alice.coin, parent_conditions)?;
149
150 let conditions = bulletin.conditions(&mut ctx)?;
151 let bulletin_spend = p2.spend_with_conditions(&mut ctx, conditions)?;
152 bulletin.spend(&mut ctx, bulletin_spend)?;
153
154 sim.spend_coins(ctx.take(), &[alice.sk])?;
155
156 let coin_spend = sim.coin_spend(bulletin.coin.coin_id()).unwrap();
157
158 let puzzle = ctx.alloc(&coin_spend.puzzle_reveal)?;
159 let puzzle = Puzzle::parse(&ctx, puzzle);
160 let solution = ctx.alloc(&coin_spend.solution)?;
161
162 let (parsed_bulletin, ..) =
163 Bulletin::parse(&mut ctx, coin_spend.coin, puzzle, solution)?.unwrap();
164
165 assert_eq!(parsed_bulletin, bulletin);
166
167 Ok(())
168 }
169}