gpl_feature_proposal/
processor.rs

1//! Program state processor
2
3use crate::{instruction::*, state::*, *};
4use gemachain_program::{
5    account_info::{next_account_info, AccountInfo},
6    clock::Clock,
7    entrypoint::ProgramResult,
8    feature::{self, Feature},
9    msg,
10    program::{invoke, invoke_signed},
11    program_error::ProgramError,
12    pubkey::Pubkey,
13    rent::Rent,
14    system_instruction,
15    sysvar::Sysvar,
16};
17
18/// Instruction processor
19pub fn process_instruction(
20    program_id: &Pubkey,
21    accounts: &[AccountInfo],
22    input: &[u8],
23) -> ProgramResult {
24    let instruction = FeatureProposalInstruction::unpack_from_slice(input)?;
25    let account_info_iter = &mut accounts.iter();
26
27    match instruction {
28        FeatureProposalInstruction::Propose {
29            tokens_to_mint,
30            acceptance_criteria,
31        } => {
32            msg!("FeatureProposalInstruction::Propose");
33
34            let funder_info = next_account_info(account_info_iter)?;
35            let feature_proposal_info = next_account_info(account_info_iter)?;
36            let mint_info = next_account_info(account_info_iter)?;
37            let distributor_token_info = next_account_info(account_info_iter)?;
38            let acceptance_token_info = next_account_info(account_info_iter)?;
39            let feature_id_info = next_account_info(account_info_iter)?;
40            let system_program_info = next_account_info(account_info_iter)?;
41            let gpl_token_program_info = next_account_info(account_info_iter)?;
42            let rent_sysvar_info = next_account_info(account_info_iter)?;
43            let rent = &Rent::from_account_info(rent_sysvar_info)?;
44
45            let (mint_address, mint_bump_seed) =
46                get_mint_address_with_seed(feature_proposal_info.key);
47            if mint_address != *mint_info.key {
48                msg!("Error: mint address derivation mismatch");
49                return Err(ProgramError::InvalidArgument);
50            }
51
52            let (distributor_token_address, distributor_token_bump_seed) =
53                get_distributor_token_address_with_seed(feature_proposal_info.key);
54            if distributor_token_address != *distributor_token_info.key {
55                msg!("Error: distributor token address derivation mismatch");
56                return Err(ProgramError::InvalidArgument);
57            }
58
59            let (acceptance_token_address, acceptance_token_bump_seed) =
60                get_acceptance_token_address_with_seed(feature_proposal_info.key);
61            if acceptance_token_address != *acceptance_token_info.key {
62                msg!("Error: acceptance token address derivation mismatch");
63                return Err(ProgramError::InvalidArgument);
64            }
65
66            let (feature_id_address, feature_id_bump_seed) =
67                get_feature_id_address_with_seed(feature_proposal_info.key);
68            if feature_id_address != *feature_id_info.key {
69                msg!("Error: feature-id address derivation mismatch");
70                return Err(ProgramError::InvalidArgument);
71            }
72
73            let mint_signer_seeds: &[&[_]] = &[
74                &feature_proposal_info.key.to_bytes(),
75                br"mint",
76                &[mint_bump_seed],
77            ];
78
79            let distributor_token_signer_seeds: &[&[_]] = &[
80                &feature_proposal_info.key.to_bytes(),
81                br"distributor",
82                &[distributor_token_bump_seed],
83            ];
84
85            let acceptance_token_signer_seeds: &[&[_]] = &[
86                &feature_proposal_info.key.to_bytes(),
87                br"acceptance",
88                &[acceptance_token_bump_seed],
89            ];
90
91            let feature_id_signer_seeds: &[&[_]] = &[
92                &feature_proposal_info.key.to_bytes(),
93                br"feature-id",
94                &[feature_id_bump_seed],
95            ];
96
97            msg!("Creating feature proposal account");
98            invoke(
99                &system_instruction::create_account(
100                    funder_info.key,
101                    feature_proposal_info.key,
102                    1.max(rent.minimum_balance(FeatureProposal::get_packed_len())),
103                    FeatureProposal::get_packed_len() as u64,
104                    program_id,
105                ),
106                &[
107                    funder_info.clone(),
108                    feature_proposal_info.clone(),
109                    system_program_info.clone(),
110                ],
111            )?;
112            FeatureProposal::Pending(acceptance_criteria)
113                .pack_into_slice(&mut feature_proposal_info.data.borrow_mut());
114
115            msg!("Creating mint");
116            invoke_signed(
117                &system_instruction::create_account(
118                    funder_info.key,
119                    mint_info.key,
120                    1.max(rent.minimum_balance(gpl_token::state::Mint::get_packed_len())),
121                    gpl_token::state::Mint::get_packed_len() as u64,
122                    &gpl_token::id(),
123                ),
124                &[
125                    funder_info.clone(),
126                    mint_info.clone(),
127                    system_program_info.clone(),
128                ],
129                &[mint_signer_seeds],
130            )?;
131
132            msg!("Initializing mint");
133            invoke(
134                &gpl_token::instruction::initialize_mint(
135                    &gpl_token::id(),
136                    mint_info.key,
137                    mint_info.key,
138                    None,
139                    gpl_token::native_mint::DECIMALS,
140                )?,
141                &[
142                    mint_info.clone(),
143                    gpl_token_program_info.clone(),
144                    rent_sysvar_info.clone(),
145                ],
146            )?;
147
148            msg!("Creating distributor token account");
149            invoke_signed(
150                &system_instruction::create_account(
151                    funder_info.key,
152                    distributor_token_info.key,
153                    1.max(rent.minimum_balance(gpl_token::state::Account::get_packed_len())),
154                    gpl_token::state::Account::get_packed_len() as u64,
155                    &gpl_token::id(),
156                ),
157                &[
158                    funder_info.clone(),
159                    distributor_token_info.clone(),
160                    system_program_info.clone(),
161                ],
162                &[distributor_token_signer_seeds],
163            )?;
164
165            msg!("Initializing distributor token account");
166            invoke(
167                &gpl_token::instruction::initialize_account(
168                    &gpl_token::id(),
169                    distributor_token_info.key,
170                    mint_info.key,
171                    feature_proposal_info.key,
172                )?,
173                &[
174                    distributor_token_info.clone(),
175                    gpl_token_program_info.clone(),
176                    rent_sysvar_info.clone(),
177                    feature_proposal_info.clone(),
178                    mint_info.clone(),
179                ],
180            )?;
181
182            msg!("Creating acceptance token account");
183            invoke_signed(
184                &system_instruction::create_account(
185                    funder_info.key,
186                    acceptance_token_info.key,
187                    1.max(rent.minimum_balance(gpl_token::state::Account::get_packed_len())),
188                    gpl_token::state::Account::get_packed_len() as u64,
189                    &gpl_token::id(),
190                ),
191                &[
192                    funder_info.clone(),
193                    acceptance_token_info.clone(),
194                    system_program_info.clone(),
195                ],
196                &[acceptance_token_signer_seeds],
197            )?;
198
199            msg!("Initializing acceptance token account");
200            invoke(
201                &gpl_token::instruction::initialize_account(
202                    &gpl_token::id(),
203                    acceptance_token_info.key,
204                    mint_info.key,
205                    feature_proposal_info.key,
206                )?,
207                &[
208                    acceptance_token_info.clone(),
209                    gpl_token_program_info.clone(),
210                    rent_sysvar_info.clone(),
211                    feature_proposal_info.clone(),
212                    mint_info.clone(),
213                ],
214            )?;
215            invoke(
216                &gpl_token::instruction::set_authority(
217                    &gpl_token::id(),
218                    acceptance_token_info.key,
219                    Some(feature_proposal_info.key),
220                    gpl_token::instruction::AuthorityType::CloseAccount,
221                    feature_proposal_info.key,
222                    &[],
223                )?,
224                &[
225                    gpl_token_program_info.clone(),
226                    acceptance_token_info.clone(),
227                    feature_proposal_info.clone(),
228                ],
229            )?;
230            invoke(
231                &gpl_token::instruction::set_authority(
232                    &gpl_token::id(),
233                    acceptance_token_info.key,
234                    Some(program_id),
235                    gpl_token::instruction::AuthorityType::AccountOwner,
236                    feature_proposal_info.key,
237                    &[],
238                )?,
239                &[
240                    gpl_token_program_info.clone(),
241                    acceptance_token_info.clone(),
242                    feature_proposal_info.clone(),
243                ],
244            )?;
245
246            // Mint `tokens_to_mint` tokens into `distributor_token_account` owned by
247            // `feature_proposal`
248            msg!("Minting {} tokens", tokens_to_mint);
249            invoke_signed(
250                &gpl_token::instruction::mint_to(
251                    &gpl_token::id(),
252                    mint_info.key,
253                    distributor_token_info.key,
254                    mint_info.key,
255                    &[],
256                    tokens_to_mint,
257                )?,
258                &[
259                    mint_info.clone(),
260                    distributor_token_info.clone(),
261                    gpl_token_program_info.clone(),
262                ],
263                &[mint_signer_seeds],
264            )?;
265
266            // Fully fund the feature id account so the `Tally` instruction will not require any
267            // carats from the caller
268            msg!("Funding feature id account");
269            invoke(
270                &system_instruction::transfer(
271                    funder_info.key,
272                    feature_id_info.key,
273                    1.max(rent.minimum_balance(Feature::size_of())),
274                ),
275                &[
276                    funder_info.clone(),
277                    feature_id_info.clone(),
278                    system_program_info.clone(),
279                ],
280            )?;
281
282            msg!("Allocating feature id account");
283            invoke_signed(
284                &system_instruction::allocate(feature_id_info.key, Feature::size_of() as u64),
285                &[feature_id_info.clone(), system_program_info.clone()],
286                &[feature_id_signer_seeds],
287            )?;
288        }
289
290        FeatureProposalInstruction::Tally => {
291            msg!("FeatureProposalInstruction::Tally");
292
293            let feature_proposal_info = next_account_info(account_info_iter)?;
294            let feature_proposal_state =
295                FeatureProposal::unpack_from_slice(&feature_proposal_info.data.borrow())?;
296
297            match feature_proposal_state {
298                FeatureProposal::Pending(acceptance_criteria) => {
299                    let acceptance_token_info = next_account_info(account_info_iter)?;
300                    let feature_id_info = next_account_info(account_info_iter)?;
301                    let system_program_info = next_account_info(account_info_iter)?;
302                    let clock_sysvar_info = next_account_info(account_info_iter)?;
303                    let clock = &Clock::from_account_info(clock_sysvar_info)?;
304
305                    // Re-derive the acceptance token and feature id program addresses to confirm
306                    // the caller provided the correct addresses
307                    let acceptance_token_address =
308                        get_acceptance_token_address(feature_proposal_info.key);
309                    if acceptance_token_address != *acceptance_token_info.key {
310                        msg!("Error: acceptance token address derivation mismatch");
311                        return Err(ProgramError::InvalidArgument);
312                    }
313
314                    let (feature_id_address, feature_id_bump_seed) =
315                        get_feature_id_address_with_seed(feature_proposal_info.key);
316                    if feature_id_address != *feature_id_info.key {
317                        msg!("Error: feature-id address derivation mismatch");
318                        return Err(ProgramError::InvalidArgument);
319                    }
320
321                    let feature_id_signer_seeds: &[&[_]] = &[
322                        &feature_proposal_info.key.to_bytes(),
323                        br"feature-id",
324                        &[feature_id_bump_seed],
325                    ];
326
327                    if clock.unix_timestamp >= acceptance_criteria.deadline {
328                        msg!("Feature proposal expired");
329                        FeatureProposal::Expired
330                            .pack_into_slice(&mut feature_proposal_info.data.borrow_mut());
331                        return Ok(());
332                    }
333
334                    msg!("Unpacking acceptance token account");
335                    let acceptance_token =
336                        gpl_token::state::Account::unpack(&acceptance_token_info.data.borrow())?;
337
338                    msg!(
339                            "Feature proposal has received {} tokens, and {} tokens required for acceptance",
340                            acceptance_token.amount, acceptance_criteria.tokens_required
341                        );
342                    if acceptance_token.amount < acceptance_criteria.tokens_required {
343                        msg!("Activation threshold has not been reached");
344                        return Ok(());
345                    }
346
347                    msg!("Assigning feature id account");
348                    invoke_signed(
349                        &system_instruction::assign(feature_id_info.key, &feature::id()),
350                        &[feature_id_info.clone(), system_program_info.clone()],
351                        &[feature_id_signer_seeds],
352                    )?;
353
354                    msg!("Feature proposal accepted");
355                    FeatureProposal::Accepted {
356                        tokens_upon_acceptance: acceptance_token.amount,
357                    }
358                    .pack_into_slice(&mut feature_proposal_info.data.borrow_mut());
359                }
360                _ => {
361                    msg!("Error: feature proposal account not in the pending state");
362                    return Err(ProgramError::InvalidAccountData);
363                }
364            }
365        }
366    }
367
368    Ok(())
369}