chia_sdk_driver/primitives/
bulletin.rs

1use 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}