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
/// See state module for description of PDA memory layout.
use crate::{
error::RuleSetError,
state::{Key, Rule},
types::{Assertable, LibVersion, RuleSet},
};
use borsh::{BorshDeserialize, BorshSerialize};
use serde::{Deserialize, Serialize};
#[cfg(feature = "serde-with-feature")]
use serde_with::{As, DisplayFromStr};
use solana_program::{entrypoint::ProgramResult, program_error::ProgramError, pubkey::Pubkey};
use std::collections::HashMap;
/// Version of the `RuleSetRevisionMapV1` struct.
pub const RULE_SET_REV_MAP_VERSION: u8 = 1;
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone)]
/// Header used to keep track of where RuleSets are stored in the PDA. This header is meant
/// to be stored at the beginning of the PDA and never be versioned so that it always
/// has the same serialized size. See top-level module for description of PDA memory layout.
pub struct RuleSetHeader {
/// The `Key` for this account which identifies it as a `RuleSet` account.
pub key: Key,
/// The location of revision map version stored in the PDA. This is one byte before the
/// revision map itself.
pub rev_map_version_location: usize,
}
impl RuleSetHeader {
/// Create a new `RuleSetHeader`.
pub fn new(rev_map_version_location: usize) -> Self {
Self {
key: Key::RuleSet,
rev_map_version_location,
}
}
}
/// Size of `RuleSetHeader` when Borsh serialized.
pub const RULE_SET_SERIALIZED_HEADER_LEN: usize = 9;
#[derive(BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug, Clone, Default)]
/// Revision map used to keep track of where individual `RuleSet` revisions are stored in the PDA.
/// See top-level module for description of PDA memory layout.
pub struct RuleSetRevisionMapV1 {
/// `Vec` used to map a `RuleSet` revision number to its location in the PDA.
pub rule_set_revisions: Vec<usize>,
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Default)]
#[serde(rename_all = "camelCase")]
/// The struct containing all Rule Set data, most importantly the map of operations to `Rules`.
/// See top-level module for description of PDA memory layout.
pub struct RuleSetV1 {
/// Version of the RuleSet. This is not a user version, but the version
/// of this lib, to make sure that a `RuleSet` passed into our handlers
/// is one we are compatible with.
lib_version: u8,
/// Owner (creator) of the RuleSet.
#[cfg_attr(feature = "serde-with-feature", serde(with = "As::<DisplayFromStr>"))]
owner: Pubkey,
/// Name of the RuleSet, used in PDA derivation.
rule_set_name: String,
/// A map to determine the `Rule` that belongs to a given `Operation`.
pub operations: HashMap<String, Rule>,
}
impl RuleSetV1 {
/// Create a new empty `RuleSet`.
pub fn new(rule_set_name: String, owner: Pubkey) -> Self {
Self {
lib_version: LibVersion::V1 as u8,
rule_set_name,
owner,
operations: HashMap::new(),
}
}
/// Add a key-value pair into a `RuleSet`. If this key is already in the `RuleSet`
/// nothing is updated and an error is returned.
pub fn add(&mut self, operation: String, rules: Rule) -> ProgramResult {
if self.operations.get(&operation).is_none() {
self.operations.insert(operation, rules);
Ok(())
} else {
Err(RuleSetError::ValueOccupied.into())
}
}
/// Retrieve the `Rule` tree for a given `Operation`.
pub fn get(&self, operation: String) -> Option<&Rule> {
self.operations.get(&operation)
}
}
impl<'a> RuleSet<'a> for RuleSetV1 {
/// Get the name of the `RuleSet`.
fn name(&self) -> String {
self.rule_set_name.clone()
}
fn owner(&self) -> &Pubkey {
&self.owner
}
fn lib_version(&self) -> u8 {
self.lib_version
}
/// This function returns the rule for an operation by recursively searching through fallbacks
fn get_rule(&self, operation: String) -> Result<&dyn Assertable<'a>, ProgramError> {
let rule = self.get(operation.to_string());
match rule {
Some(Rule::Namespace) => {
// Check for a ':' namespace separator. If it exists try to operation namespace to see if
// a fallback exists. E.g. 'transfer:owner' will check for a fallback for 'transfer'.
// If it doesn't exist then fail.
let split = operation.split(':').collect::<Vec<&str>>();
if split.len() > 1 {
self.get_rule(split[0].to_owned())
} else {
Err(RuleSetError::OperationNotFound.into())
}
}
Some(r) => Ok(r),
None => Err(RuleSetError::OperationNotFound.into()),
}
}
}