1use crate::events::{CompressedPostDeleted, CompressedPostNew, CompressedPostUpdated};
2use crate::state::TreeConfig;
3use crate::utils::{append_leaf, replace_leaf, try_find_asset_id, LeafSchema};
4use anchor_lang::Discriminator;
5
6use gpl_core::errors::PostError;
7use spl_account_compression::wrap_application_data_v1;
8use spl_account_compression::Node;
9
10use gpl_core::state::{Post, Profile, User, MAX_LEN_URI};
11
12use anchor_lang::prelude::*;
13use std::convert::AsRef;
14
15use gpl_core::constants::*;
16
17use anchor_lang::solana_program::keccak::hashv;
18use gpl_core::program::GplCore;
19use spl_account_compression::program::SplAccountCompression;
20use spl_account_compression::Noop;
21
22#[derive(Accounts)]
24#[instruction(metadata_uri: String, random_hash: [u8;32])]
25pub struct CreateCompressedPost<'info> {
26 #[account(
27 seeds = [
28 PROFILE_PREFIX_SEED.as_bytes(),
29 profile.namespace.as_ref().as_bytes(),
30 user.to_account_info().key.as_ref(),
31 ],
32 seeds::program = gpl_core_program.key(),
33 bump,
34 has_one = user,
35 )]
36 pub profile: Account<'info, Profile>,
37 #[account(
38 seeds = [
39 USER_PREFIX_SEED.as_bytes(),
40 user.random_hash.as_ref(),
41 ],
42 seeds::program = gpl_core_program.key(),
43 bump,
44 has_one = authority,
45 )]
46 pub user: Account<'info, User>,
47
48 #[account(seeds = [merkle_tree.key.as_ref()], bump)]
49 pub tree_config: Account<'info, TreeConfig>,
50
51 #[account(mut)]
52 pub merkle_tree: UncheckedAccount<'info>,
54
55 #[account(mut)]
56 pub authority: Signer<'info>,
57
58 pub compression_program: Program<'info, SplAccountCompression>,
59 pub log_wrapper_program: Program<'info, Noop>,
60 pub gpl_core_program: Program<'info, GplCore>,
61 pub system_program: Program<'info, System>,
62}
63
64pub fn create_compressed_post_handler(
66 ctx: Context<CreateCompressedPost>,
67 metadata_uri: String,
68 random_hash: [u8; 32],
69) -> Result<()> {
70 require!(metadata_uri.len() <= MAX_LEN_URI, PostError::URITooLong);
71
72 let post_seeds = [POST_PREFIX_SEED.as_bytes(), random_hash.as_ref()];
73
74 let (post_id, post_bump) =
75 Pubkey::try_find_program_address(&post_seeds, &GplCore::id()).unwrap();
76
77 let seed_hash = hashv(&post_seeds).to_bytes();
78
79 let asset_id = try_find_asset_id(ctx.accounts.merkle_tree.key, seed_hash)?;
80
81 let post = Post {
82 metadata_uri,
83 random_hash,
84 profile: *ctx.accounts.profile.to_account_info().key,
85 reply_to: None,
86 };
87
88 let leaf = LeafSchema {
89 asset_id,
90 seed_hash,
91 data_hash: hashv(&[&Post::DISCRIMINATOR, &post.try_to_vec()?]).to_bytes(),
92 };
93
94 let leaf_node = leaf.to_node()?;
95
96 wrap_application_data_v1(leaf_node.to_vec(), &ctx.accounts.log_wrapper_program)?;
97
98 append_leaf(
99 ctx.accounts.merkle_tree.key,
100 ctx.bumps["tree_config"],
101 &ctx.accounts.authority.to_account_info(),
102 leaf_node,
103 &ctx.accounts.merkle_tree,
104 &ctx.accounts.compression_program,
105 &ctx.accounts.log_wrapper_program,
106 )?;
107
108 emit!(CompressedPostNew {
109 asset_id,
110 post_id,
111 post_bump,
112 profile: *ctx.accounts.profile.to_account_info().key,
113 user: *ctx.accounts.user.to_account_info().key,
114 random_hash: random_hash,
115 metadata_uri: post.metadata_uri.clone(),
116 timestamp: Clock::get()?.unix_timestamp,
117 index: 0 });
119
120 Ok(())
121}
122
123#[derive(Accounts)]
125#[instruction(metadata_uri: String, new_metadata_uri: String, random_hash: [u8;32], root: [u8;32], index: u32)]
126pub struct UpdateCompressedPost<'info> {
127 #[account(
128 seeds = [
129 PROFILE_PREFIX_SEED.as_bytes(),
130 profile.namespace.as_ref().as_bytes(),
131 user.to_account_info().key.as_ref(),
132 ],
133 seeds::program = gpl_core_program.key(),
134 bump,
135 has_one = user,
136 )]
137 pub profile: Account<'info, Profile>,
138 #[account(
139 seeds = [
140 USER_PREFIX_SEED.as_bytes(),
141 user.random_hash.as_ref(),
142 ],
143 seeds::program = gpl_core_program.key(),
144 bump,
145 has_one = authority,
146 )]
147 pub user: Account<'info, User>,
148 #[account(seeds = [merkle_tree.key.as_ref()], bump)]
149 pub tree_config: Account<'info, TreeConfig>,
150
151 #[account(mut)]
152 pub merkle_tree: UncheckedAccount<'info>,
154
155 #[account(mut)]
156 pub authority: Signer<'info>,
157
158 pub compression_program: Program<'info, SplAccountCompression>,
159 pub log_wrapper_program: Program<'info, Noop>,
160 pub gpl_core_program: Program<'info, GplCore>,
161 pub system_program: Program<'info, System>,
162}
163
164pub fn update_compressed_post_handler<'info>(
166 ctx: Context<'_, '_, '_, 'info, UpdateCompressedPost<'info>>,
167 metadata_uri: String,
168 new_metadata_uri: String,
169 random_hash: [u8; 32],
170 root: [u8; 32],
171 index: u32,
172) -> Result<()> {
173 require!(metadata_uri.len() <= MAX_LEN_URI, PostError::URITooLong);
175
176 let post_seeds = [POST_PREFIX_SEED.as_bytes(), random_hash.as_ref()];
177
178 let (post_id, post_bump) =
179 Pubkey::try_find_program_address(&post_seeds, &GplCore::id()).unwrap();
180
181 let seed_hash = hashv(&post_seeds).to_bytes();
182
183 let asset_id = try_find_asset_id(ctx.accounts.merkle_tree.key, seed_hash)?;
184
185 let old_post = Post {
186 metadata_uri,
187 random_hash,
188 profile: *ctx.accounts.profile.to_account_info().key,
189 reply_to: None,
190 };
191
192 let old_leaf = LeafSchema {
193 asset_id,
194 seed_hash,
195 data_hash: hashv(&[&Post::DISCRIMINATOR, &old_post.try_to_vec()?]).to_bytes(),
197 };
198
199 let old_leaf_node = old_leaf.to_node()?;
200
201 let new_post = Post {
202 metadata_uri: new_metadata_uri,
203 random_hash,
204 profile: *ctx.accounts.profile.to_account_info().key,
205 reply_to: None,
206 };
207
208 let new_leaf = LeafSchema {
209 asset_id,
210 seed_hash,
211 data_hash: hashv(&[&Post::DISCRIMINATOR, &new_post.try_to_vec()?]).to_bytes(),
213 };
214
215 let new_leaf_node = new_leaf.to_node()?;
216
217 wrap_application_data_v1(new_leaf_node.to_vec(), &ctx.accounts.log_wrapper_program)?;
218
219 replace_leaf(
220 ctx.accounts.merkle_tree.key,
221 ctx.bumps["tree_config"],
222 &ctx.accounts.authority.to_account_info(),
223 &ctx.accounts.merkle_tree,
224 root,
225 old_leaf_node,
226 new_leaf_node,
227 index,
228 ctx.remaining_accounts,
229 &ctx.accounts.compression_program,
230 &ctx.accounts.log_wrapper_program,
231 )?;
232
233 emit!(CompressedPostUpdated {
235 asset_id,
236 post_id,
237 post_bump,
238 profile: *ctx.accounts.profile.to_account_info().key,
239 user: *ctx.accounts.user.to_account_info().key,
240 random_hash: random_hash,
241 metadata_uri: new_post.metadata_uri.clone(),
242 timestamp: Clock::get()?.unix_timestamp,
243 index: index
244 });
245
246 Ok(())
247}
248
249#[derive(Accounts)]
251#[instruction(metadata_uri: String, random_hash: [u8;32], root: [u8;32], index: u32)]
253pub struct DeleteCompressedPost<'info> {
254 #[account(
255 seeds = [
256 PROFILE_PREFIX_SEED.as_bytes(),
257 profile.namespace.as_ref().as_bytes(),
258 user.to_account_info().key.as_ref(),
259 ],
260 seeds::program = gpl_core_program.key(),
261 bump,
262 has_one = user,
263 )]
264 pub profile: Account<'info, Profile>,
265 #[account(
266 seeds = [
267 USER_PREFIX_SEED.as_bytes(),
268 user.random_hash.as_ref(),
269 ],
270 seeds::program = gpl_core_program.key(),
271 bump,
272 has_one = authority,
273 )]
274 pub user: Account<'info, User>,
275
276 #[account(seeds = [merkle_tree.key.as_ref()], bump)]
277 pub tree_config: Account<'info, TreeConfig>,
278
279 #[account(mut)]
280 pub merkle_tree: UncheckedAccount<'info>,
282
283 #[account(mut)]
284 pub authority: Signer<'info>,
285 pub compression_program: Program<'info, SplAccountCompression>,
286 pub log_wrapper_program: Program<'info, Noop>,
287 pub gpl_core_program: Program<'info, GplCore>,
288 pub system_program: Program<'info, System>,
289}
290
291pub fn delete_compressed_post_handler<'info>(
293 ctx: Context<'_, '_, '_, 'info, DeleteCompressedPost<'info>>,
294 metadata_uri: String,
295 random_hash: [u8; 32],
296 root: [u8; 32],
297 index: u32,
298) -> Result<()> {
299 require!(metadata_uri.len() <= MAX_LEN_URI, PostError::URITooLong);
301
302 let post_seeds = [POST_PREFIX_SEED.as_bytes(), random_hash.as_ref()];
303
304 let (post_id, post_bump) =
305 Pubkey::try_find_program_address(&post_seeds, &GplCore::id()).unwrap();
306
307 let seed_hash = hashv(&post_seeds).to_bytes();
308
309 let asset_id = try_find_asset_id(ctx.accounts.merkle_tree.key, seed_hash)?;
310
311 let old_post = Post {
312 metadata_uri,
313 random_hash,
314 profile: *ctx.accounts.profile.to_account_info().key,
315 reply_to: None,
316 };
317
318 let old_leaf = LeafSchema {
319 asset_id,
320 seed_hash,
321 data_hash: hashv(&[&Post::DISCRIMINATOR, &old_post.try_to_vec()?]).to_bytes(),
323 };
324
325 let old_leaf_node = old_leaf.to_node()?;
326
327 let new_leaf_node = Node::default();
328
329 wrap_application_data_v1(new_leaf_node.to_vec(), &ctx.accounts.log_wrapper_program)?;
330
331 replace_leaf(
332 ctx.accounts.merkle_tree.key,
333 ctx.bumps["tree_config"],
334 &ctx.accounts.authority.to_account_info(),
335 &ctx.accounts.merkle_tree,
336 root,
337 old_leaf_node,
338 new_leaf_node,
339 index,
340 ctx.remaining_accounts,
341 &ctx.accounts.compression_program,
342 &ctx.accounts.log_wrapper_program,
343 )?;
344
345 emit!(CompressedPostDeleted {
347 asset_id,
348 post_id,
349 post_bump,
350 profile: *ctx.accounts.profile.to_account_info().key,
351 user: *ctx.accounts.user.to_account_info().key,
352 random_hash: random_hash,
353 metadata_uri: old_post.metadata_uri.clone(),
354 timestamp: Clock::get()?.unix_timestamp,
355 index: index
356 });
357
358 Ok(())
359}