1use anchor_lang::Discriminator;
2use std::convert::AsRef;
3use std::str::FromStr;
4
5use gpl_core::state::Reaction;
6use gpl_core::state::ReactionType;
7use gpl_core::state::{Profile, User};
8
9use anchor_lang::prelude::*;
10use anchor_lang::solana_program::keccak::hashv;
11
12use gpl_core::constants::*;
13
14use gpl_core::program::GplCore;
15use spl_account_compression::program::SplAccountCompression;
16use spl_account_compression::wrap_application_data_v1;
17use spl_account_compression::Node;
18use spl_account_compression::Noop;
19
20use crate::events::{CompressedReactionDeleted, CompressedReactionNew};
21use crate::state::TreeConfig;
22use crate::utils::verify_leaf;
23use crate::utils::LeafSchema;
24use crate::utils::{append_leaf, replace_leaf, try_find_asset_id};
25
26#[derive(Accounts)]
28#[instruction(to_post: Pubkey, reaction_type: String, post_root: [u8; 32], post_leaf: [u8; 32], post_index: u32)]
29pub struct CreateCompressedReaction<'info> {
30 #[account(
31 seeds = [
32 PROFILE_PREFIX_SEED.as_bytes(),
33 from_profile.namespace.as_ref().as_bytes(),
34 user.to_account_info().key.as_ref(),
35 ],
36 seeds::program = gpl_core_program.key(),
37 bump,
38 has_one = user,
39 )]
40 pub from_profile: Account<'info, Profile>,
41
42 #[account(
43 seeds = [
44 USER_PREFIX_SEED.as_bytes(),
45 user.random_hash.as_ref(),
46 ],
47 seeds::program = gpl_core_program.key(),
48 bump,
49 has_one = authority,
50 )]
51 pub user: Account<'info, User>,
52
53 #[account(seeds = [merkle_tree.key.as_ref()], bump)]
54 pub tree_config: Account<'info, TreeConfig>,
55
56 #[account(mut)]
57 pub merkle_tree: UncheckedAccount<'info>,
59
60 #[account(seeds = [target_merkle_tree.key.as_ref()], bump)]
62 pub target_tree_config: Account<'info, TreeConfig>,
63
64 pub target_merkle_tree: UncheckedAccount<'info>,
66
67 #[account(mut)]
68 pub authority: Signer<'info>,
69
70 pub compression_program: Program<'info, SplAccountCompression>,
71 pub log_wrapper_program: Program<'info, Noop>,
72 pub gpl_core_program: Program<'info, GplCore>,
73 pub system_program: Program<'info, System>,
74}
75
76pub fn create_compressed_reaction_handler<'info>(
78 ctx: Context<'_, '_, '_, 'info, CreateCompressedReaction<'info>>,
79 to_post: Pubkey,
80 reaction_type: String,
81 post_root: [u8; 32],
82 post_leaf: [u8; 32],
83 post_index: u32,
84) -> Result<()> {
85 let from_profile = &ctx.accounts.from_profile;
86
87 verify_leaf(
92 ctx.accounts.target_merkle_tree.key,
93 ctx.bumps["target_tree_config"],
94 post_root,
95 post_leaf,
96 post_index,
97 ctx.remaining_accounts,
98 &ctx.accounts.target_merkle_tree,
99 &ctx.accounts.compression_program,
100 )?;
101
102 let reaction_seeds = [
103 REACTION_PREFIX_SEED.as_bytes(),
104 reaction_type.as_bytes(),
105 to_post.as_ref(),
106 from_profile.to_account_info().key.as_ref(),
107 ];
108
109 let (reaction_id, reaction_bump) =
110 Pubkey::try_find_program_address(&reaction_seeds, &GplCore::id()).unwrap();
111
112 let seed_hash = hashv(&reaction_seeds).to_bytes();
113
114 let asset_id = try_find_asset_id(ctx.accounts.merkle_tree.key, seed_hash)?;
115
116 let reaction = Reaction {
117 from_profile: *from_profile.to_account_info().key,
118 to_post,
119 reaction_type: ReactionType::from_str(&reaction_type).unwrap(),
120 };
121
122 let leaf = LeafSchema {
123 asset_id,
124 seed_hash,
125 data_hash: hashv(&[&Reaction::DISCRIMINATOR, &reaction.try_to_vec()?]).to_bytes(),
126 };
127
128 let leaf_node = leaf.to_node()?;
129
130 wrap_application_data_v1(leaf_node.to_vec(), &ctx.accounts.log_wrapper_program)?;
131
132 append_leaf(
133 ctx.accounts.merkle_tree.key,
134 ctx.bumps["tree_config"],
135 &ctx.accounts.authority.to_account_info(),
136 leaf_node,
137 &ctx.accounts.merkle_tree,
138 &ctx.accounts.compression_program,
139 &ctx.accounts.log_wrapper_program,
140 )?;
141
142 emit!(CompressedReactionNew {
144 from_profile: *from_profile.to_account_info().key,
145 to_post,
146 reaction_type: reaction_type.clone(),
147 reaction_id,
148 reaction_bump,
149 asset_id,
150 index: 0, user: *ctx.accounts.user.to_account_info().key,
152 timestamp: Clock::get()?.unix_timestamp,
153 });
154
155 Ok(())
156}
157
158#[derive(Accounts)]
160#[instruction(to_post: Pubkey, reaction_type: String, root: [u8; 32], index: u32)]
161pub struct DeleteCompressedReaction<'info> {
162 #[account(
163 seeds = [
164 PROFILE_PREFIX_SEED.as_bytes(),
165 from_profile.namespace.as_ref().as_bytes(),
166 user.to_account_info().key.as_ref(),
167 ],
168 seeds::program = gpl_core_program.key(),
169 bump,
170 has_one = user,
171 )]
172 pub from_profile: Account<'info, Profile>,
173
174 #[account(
175 seeds = [
176 USER_PREFIX_SEED.as_bytes(),
177 user.random_hash.as_ref(),
178 ],
179 seeds::program = gpl_core_program.key(),
180 bump,
181 has_one = authority,
182 )]
183 pub user: Account<'info, User>,
184
185 #[account(seeds = [merkle_tree.key.as_ref()], bump)]
186 pub tree_config: Account<'info, TreeConfig>,
187
188 #[account(mut)]
189 pub merkle_tree: UncheckedAccount<'info>,
191
192 #[account(mut)]
193 pub authority: Signer<'info>,
194
195 pub compression_program: Program<'info, SplAccountCompression>,
196 pub log_wrapper_program: Program<'info, Noop>,
197 pub gpl_core_program: Program<'info, GplCore>,
198 pub system_program: Program<'info, System>,
199}
200
201pub fn delete_compressed_reaction_handler<'info>(
203 ctx: Context<'_, '_, '_, 'info, DeleteCompressedReaction<'info>>,
204 to_post: Pubkey,
205 reaction_type: String,
206 root: [u8; 32],
207 index: u32,
208) -> Result<()> {
209 let from_profile = &ctx.accounts.from_profile;
210 let reaction_seeds = [
211 REACTION_PREFIX_SEED.as_bytes(),
212 reaction_type.as_bytes(),
213 to_post.as_ref(),
214 from_profile.to_account_info().key.as_ref(),
215 ];
216
217 let (reaction_id, reaction_bump) =
218 Pubkey::try_find_program_address(&reaction_seeds, &GplCore::id()).unwrap();
219
220 let seed_hash = hashv(&reaction_seeds).to_bytes();
221
222 let asset_id = try_find_asset_id(ctx.accounts.merkle_tree.key, seed_hash)?;
223
224 let old_reaction = Reaction {
225 from_profile: *from_profile.to_account_info().key,
226 to_post,
227 reaction_type: ReactionType::from_str(&reaction_type).unwrap(),
228 };
229
230 let old_leaf = LeafSchema {
231 asset_id,
232 seed_hash,
233 data_hash: hashv(&[&Reaction::DISCRIMINATOR, &old_reaction.try_to_vec()?]).to_bytes(),
234 };
235
236 let old_leaf_node = old_leaf.to_node()?;
237
238 let new_leaf_node = Node::default();
239
240 wrap_application_data_v1(new_leaf_node.to_vec(), &ctx.accounts.log_wrapper_program)?;
241
242 replace_leaf(
243 ctx.accounts.merkle_tree.key,
244 ctx.bumps["tree_config"],
245 &ctx.accounts.authority.to_account_info(),
246 &ctx.accounts.merkle_tree,
247 root,
248 old_leaf_node,
249 new_leaf_node,
250 index,
251 ctx.remaining_accounts,
252 &ctx.accounts.compression_program,
253 &ctx.accounts.log_wrapper_program,
254 )?;
255
256 emit!(CompressedReactionDeleted {
258 from_profile: *from_profile.to_account_info().key,
259 to_post,
260 reaction_type: reaction_type.clone(),
261 reaction_id,
262 reaction_bump,
263 asset_id,
264 index,
265 user: *ctx.accounts.user.to_account_info().key,
266 timestamp: Clock::get()?.unix_timestamp,
267 });
268
269 Ok(())
270}