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>, 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 Ok(Some(Self::new(
109 coin,
110 bulletin_layer.inner_puzzle.tree_hash().into(),
111 messages,
112 )))
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use anyhow::Result;
119 use chia_sdk_test::Simulator;
120
121 use crate::{SpendWithConditions, StandardLayer};
122
123 use super::*;
124
125 #[test]
126 fn test_bulletin() -> Result<()> {
127 let mut sim = Simulator::new();
128 let mut ctx = SpendContext::new();
129
130 let alice = sim.bls(0);
131 let p2 = StandardLayer::new(alice.pk);
132
133 let (parent_conditions, bulletin) = Bulletin::create(
134 alice.coin.coin_id(),
135 alice.puzzle_hash,
136 vec![BulletinMessage::new(
137 "animals/rabbit/vienna-blue".to_string(),
138 "The Vienna Blue rabbit breed originally comes from Austria.".to_string(),
139 )],
140 )?;
141
142 p2.spend(&mut ctx, alice.coin, parent_conditions)?;
143
144 let conditions = bulletin.conditions(&mut ctx)?;
145 let bulletin_spend = p2.spend_with_conditions(&mut ctx, conditions)?;
146 bulletin.spend(&mut ctx, bulletin_spend)?;
147
148 sim.spend_coins(ctx.take(), &[alice.sk])?;
149
150 let coin_spend = sim.coin_spend(bulletin.coin.coin_id()).unwrap();
151
152 let puzzle = ctx.alloc(&coin_spend.puzzle_reveal)?;
153 let puzzle = Puzzle::parse(&ctx, puzzle);
154 let solution = ctx.alloc(&coin_spend.solution)?;
155
156 let parsed_bulletin =
157 Bulletin::parse(&mut ctx, coin_spend.coin, puzzle, solution)?.unwrap();
158
159 assert_eq!(parsed_bulletin, bulletin);
160
161 Ok(())
162 }
163}