gpl_compression/instructions/
reaction.rs

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// Create Reaction
27#[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    /// CHECK The account must have the same authority as that of the config
58    pub merkle_tree: UncheckedAccount<'info>,
59
60    // TODO: The seeds should be more descriptive
61    #[account(seeds = [target_merkle_tree.key.as_ref()], bump)]
62    pub target_tree_config: Account<'info, TreeConfig>,
63
64    /// CHECK The account must have the same authority as that of the config
65    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
76// Handler to create a compressed reaction
77pub 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    // Check if the to_post exists
88    // FIXME:
89    // They can potentially pass any proof. How do we verify this belongs to the asset unless we
90    // construct the leaf ourselves?
91    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 a compressed reaction event
143    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, //TODO: get the index
151        user: *ctx.accounts.user.to_account_info().key,
152        timestamp: Clock::get()?.unix_timestamp,
153    });
154
155    Ok(())
156}
157
158// Delete a compressed reaction
159#[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    /// CHECK The account must have the same authority as that of the config
190    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
201// Handler to delete a compressed reaction
202pub 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 a compressed reaction delete event
257    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}