1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
use crate::payload::Payload;
use borsh::{BorshDeserialize, BorshSerialize};
use mpl_token_metadata_context_derive::AccountContext;
use shank::ShankInstruction;
use solana_program::{
    account_info::AccountInfo,
    instruction::{AccountMeta, Instruction},
};

#[repr(C)]
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)]
/// Args for `create` instruction.
pub enum CreateOrUpdateArgs {
    /// V1 implementation of the `create` instruction arguments.
    V1 {
        /// RuleSet pre-serialized by caller into the MessagePack format.
        serialized_rule_set: Vec<u8>,
    },
}

#[repr(C)]
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)]
/// Args for `validate` instruction.
pub enum ValidateArgs {
    /// V1 implementation of the `validate` instruction arguments.
    V1 {
        /// `Operation` to validate.
        operation: String,
        /// `Payload` data used for rule validation.
        payload: Payload,
        /// Update any relevant state stored in Rule, such as the Frequency `last_update` time value.
        update_rule_state: bool,
        /// Optional revision of the `RuleSet` to use.  If `None`, the latest revision is used.
        rule_set_revision: Option<usize>,
    },
}

#[repr(C)]
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)]
/// Args for `append_to_rule_set` instruction.
pub enum WriteToBufferArgs {
    /// V1 implementation of the `create` instruction arguments.
    V1 {
        /// RuleSet pre-serialized by caller into the MessagePack format.
        serialized_rule_set: Vec<u8>,
        /// Whether the or not the any old data should be overwritten.
        overwrite: bool,
    },
}

#[repr(C)]
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)]
/// Args for `append_to_rule_set` instruction.
pub enum PuffRuleSetArgs {
    /// V1 implementation of the `create` instruction arguments.
    V1 {
        /// RuleSet name.
        rule_set_name: String,
    },
}

#[derive(Debug, Clone, ShankInstruction, AccountContext, BorshSerialize, BorshDeserialize)]
#[rustfmt::skip]
/// Instructions available in this program.
pub enum RuleSetInstruction {
    /// This instruction stores a caller-pre-serialized `RuleSet` into the rule_set PDA account.
    #[account(0, signer, writable, name="payer", desc="Payer and creator of the RuleSet")]
    #[account(1, writable, name="rule_set_pda", desc = "The PDA account where the RuleSet is stored")]
    #[account(2, name = "system_program", desc = "System program")]
    #[account(3, optional, name="buffer_pda", desc = "The buffer to copy a complete ruleset from")]
    #[default_optional_accounts]
    CreateOrUpdate(CreateOrUpdateArgs),

    /// This instruction executes the RuleSet stored in the rule_set PDA account by calling the
    /// `RuleSet`'s `validate` method.  If any of the Rules contained in the RuleSet have state
    /// information (such as the Frequency rule's `last_update` time value), the optional accounts
    /// must be provided in order to save the updated stated in the RuleSet state PDA.  Note that
    /// updating the state for a Rule requires that the `rule_authority` signer matches the Pubkey
    /// stored in the Rule.
    #[account(0, name="rule_set_pda", desc = "The PDA account where the RuleSet is stored")]
    #[account(1, name="mint", desc="Mint of token asset")]
    #[account(2, name = "system_program", desc = "System program")]
    #[account(3, optional, signer, writable, name="payer", desc="Payer for RuleSet state PDA account")]
    #[account(4, optional, signer, name="rule_authority", desc="Signing authority for any Rule state updates")]
    #[account(5, optional, writable, name="rule_set_state_pda", desc = "The PDA account where any RuleSet state is stored")]
    #[args(additional_rule_accounts: Vec<AccountMeta>)]
    #[default_optional_accounts]
    Validate(ValidateArgs),

    /// This instruction appends a pre-serialized `RuleSet` chunk into the rule_set PDA account.
    /// Needed with large `RuleSet`s to stay within transaction size limit.
    #[account(0, signer, writable, name="payer", desc="Payer and creator of the RuleSet")]
    #[account(1, writable, name="buffer_pda", desc = "The PDA account where the RuleSet buffer is stored")]
    #[account(2, name = "system_program", desc = "System program")]
    WriteToBuffer(WriteToBufferArgs),

    /// Add space to the end of a rule set account.  Needed with large `RuleSet`s to pre-allocate
    /// the space, to stay within PDA allocation limits.
    #[account(0, signer, writable, name="payer", desc="Payer and creator of the RuleSet")]
    #[account(1, writable, name="rule_set_pda", desc = "The PDA account where the RuleSet is stored")]
    #[account(2, name = "system_program", desc = "System program")]
    PuffRuleSet(PuffRuleSetArgs),
}

/// Builds a `CreateOrUpdate` instruction.
impl InstructionBuilder for builders::CreateOrUpdate {
    fn instruction(&self) -> solana_program::instruction::Instruction {
        let mut accounts = vec![
            AccountMeta::new(self.payer, true),
            AccountMeta::new(self.rule_set_pda, false),
            AccountMeta::new_readonly(solana_program::system_program::id(), false),
        ];

        if let Some(buffer_pda) = self.buffer_pda {
            accounts.push(AccountMeta::new_readonly(buffer_pda, false));
        } else {
            accounts.push(AccountMeta::new_readonly(crate::ID, false));
        }

        Instruction {
            program_id: crate::ID,
            accounts,
            data: RuleSetInstruction::CreateOrUpdate(self.args.clone())
                .try_to_vec()
                .unwrap(),
        }
    }
}

/// Builds a `Validate` instruction.
impl InstructionBuilder for builders::Validate {
    fn instruction(&self) -> solana_program::instruction::Instruction {
        let mut accounts = vec![
            AccountMeta::new_readonly(self.rule_set_pda, false),
            AccountMeta::new_readonly(self.mint, false),
            AccountMeta::new_readonly(solana_program::system_program::id(), false),
        ];

        // Add optional account or `crate::ID`.
        if let Some(payer) = self.payer {
            accounts.push(AccountMeta::new(payer, true));
        } else {
            accounts.push(AccountMeta::new_readonly(crate::ID, false));
        }

        // Add optional account or `crate::ID`.
        if let Some(rule_authority) = self.rule_authority {
            accounts.push(AccountMeta::new_readonly(rule_authority, true));
        } else {
            accounts.push(AccountMeta::new_readonly(crate::ID, false));
        }

        // Add optional account or `crate::ID`.
        if let Some(rule_set_state_pda) = self.rule_set_state_pda {
            accounts.push(AccountMeta::new(rule_set_state_pda, false));
        } else {
            accounts.push(AccountMeta::new_readonly(crate::ID, false));
        }

        accounts.extend(self.additional_rule_accounts.clone());

        Instruction {
            program_id: crate::ID,
            accounts,
            data: RuleSetInstruction::Validate(self.args.clone())
                .try_to_vec()
                .unwrap(),
        }
    }
}

/// Builds a `WriteToBuffer` instruction.
impl InstructionBuilder for builders::WriteToBuffer {
    fn instruction(&self) -> solana_program::instruction::Instruction {
        let accounts = vec![
            AccountMeta::new(self.payer, true),
            AccountMeta::new(self.buffer_pda, false),
            AccountMeta::new_readonly(solana_program::system_program::id(), false),
        ];

        Instruction {
            program_id: crate::ID,
            accounts,
            data: RuleSetInstruction::WriteToBuffer(self.args.clone())
                .try_to_vec()
                .unwrap(),
        }
    }
}

/// Builds a `PuffRuleSet` instruction.
impl InstructionBuilder for builders::PuffRuleSet {
    fn instruction(&self) -> solana_program::instruction::Instruction {
        let accounts = vec![
            AccountMeta::new(self.payer, true),
            AccountMeta::new(self.rule_set_pda, false),
            AccountMeta::new_readonly(solana_program::system_program::id(), false),
        ];

        Instruction {
            program_id: crate::ID,
            accounts,
            data: RuleSetInstruction::PuffRuleSet(self.args.clone())
                .try_to_vec()
                .unwrap(),
        }
    }
}

/// Account context holding the accounts used by various instructions.
pub struct Context<'a, T> {
    /// The struct holding the named accounts used by an instruction.
    pub accounts: T,
    /// All remaining accounts passed to an instruction.
    pub remaining_accounts: Vec<&'a AccountInfo<'a>>,
}

/// A trait for building an instruction.
pub trait InstructionBuilder {
    /// The required function to return the built instruction.
    fn instruction(&self) -> solana_program::instruction::Instruction;
}