use std::{collections::HashMap, fmt, marker::PhantomData, sync::Arc};
use agave_feature_set::FeatureSet;
use solana_address::Address;
use solana_precompile_error::PrecompileError;
use crate::SvmTypesHost;
pub trait Precompile<T: SvmTypesHost>: Send + Sync {
fn program_id(&self) -> Address;
fn is_enabled(&self, _feature_set: &FeatureSet) -> bool {
true
}
fn verify(
&self,
data: &[u8],
slices: &[&[u8]],
feature_set: &FeatureSet,
) -> Result<(), PrecompileError>;
}
pub struct PrecompileSet<T: SvmTypesHost> {
entries: HashMap<Address, Arc<dyn Precompile<T>>>,
_marker: PhantomData<fn() -> T>,
}
impl<T: SvmTypesHost> PrecompileSet<T> {
pub fn new() -> Self {
Self { entries: HashMap::new(), _marker: PhantomData }
}
pub fn insert(&mut self, precompile: Arc<dyn Precompile<T>>) -> Option<Arc<dyn Precompile<T>>> {
self.entries.insert(precompile.program_id(), precompile)
}
pub fn get(&self, program_id: &Address) -> Option<Arc<dyn Precompile<T>>> {
self.entries.get(program_id).cloned()
}
pub fn contains(&self, program_id: &Address) -> bool {
self.entries.contains_key(program_id)
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn program_ids(&self) -> Vec<Address> {
self.entries.keys().cloned().collect()
}
}
impl<T: SvmTypesHost> Clone for PrecompileSet<T> {
fn clone(&self) -> Self {
Self { entries: self.entries.clone(), _marker: PhantomData }
}
}
impl<T: SvmTypesHost> Default for PrecompileSet<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: SvmTypesHost> fmt::Debug for PrecompileSet<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PrecompileSet").field("program_ids", &self.program_ids()).finish()
}
}
pub trait PrecompileProvider<T: SvmTypesHost>: Send + Sync {
fn get(&self, program_id: &Address) -> Option<Arc<dyn Precompile<T>>>;
fn program_ids(&self) -> Vec<Address>;
}
impl<T: SvmTypesHost> PrecompileProvider<T> for PrecompileSet<T> {
fn get(&self, program_id: &Address) -> Option<Arc<dyn Precompile<T>>> {
self.entries.get(program_id).cloned()
}
fn program_ids(&self) -> Vec<Address> {
self.entries.keys().cloned().collect()
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct NoPrecompiles;
impl<T: SvmTypesHost> PrecompileProvider<T> for NoPrecompiles {
fn get(&self, _program_id: &Address) -> Option<Arc<dyn Precompile<T>>> {
None
}
fn program_ids(&self) -> Vec<Address> {
Vec::new()
}
}
#[cfg(feature = "precompiles")]
pub struct NativePrecompiles<T: SvmTypesHost> {
set: PrecompileSet<T>,
}
#[cfg(feature = "precompiles")]
impl<T: SvmTypesHost> NativePrecompiles<T> {
pub fn new() -> Self {
let mut set = PrecompileSet::new();
for precompile in agave_precompiles::get_precompiles() {
set.insert(Arc::new(NativePrecompile {
program_id: precompile.program_id,
feature: precompile.feature,
verify_fn: precompile.verify_fn,
}));
}
Self { set }
}
pub fn into_set(self) -> PrecompileSet<T> {
self.set
}
}
#[cfg(feature = "precompiles")]
impl<T: SvmTypesHost> Clone for NativePrecompiles<T> {
fn clone(&self) -> Self {
Self { set: self.set.clone() }
}
}
#[cfg(feature = "precompiles")]
impl<T: SvmTypesHost> Default for NativePrecompiles<T> {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "precompiles")]
impl<T: SvmTypesHost> fmt::Debug for NativePrecompiles<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("NativePrecompiles").field("program_ids", &self.set.program_ids()).finish()
}
}
#[cfg(feature = "precompiles")]
impl<T: SvmTypesHost> PrecompileProvider<T> for NativePrecompiles<T> {
fn get(&self, program_id: &Address) -> Option<Arc<dyn Precompile<T>>> {
self.set.entries.get(program_id).cloned()
}
fn program_ids(&self) -> Vec<Address> {
self.set.program_ids()
}
}
#[cfg(feature = "precompiles")]
#[derive(Clone, Copy, Debug)]
struct NativePrecompile {
program_id: Address,
feature: Option<Address>,
verify_fn: agave_precompiles::Verify,
}
#[cfg(feature = "precompiles")]
impl NativePrecompile {
fn enabled(&self, feature_set: &FeatureSet) -> bool {
self.feature.is_none_or(|feature| feature_set.is_active(&feature))
}
}
#[cfg(feature = "precompiles")]
impl<T: SvmTypesHost> Precompile<T> for NativePrecompile {
fn program_id(&self) -> Address {
self.program_id
}
fn is_enabled(&self, feature_set: &FeatureSet) -> bool {
self.enabled(feature_set)
}
fn verify(
&self,
data: &[u8],
slices: &[&[u8]],
feature_set: &FeatureSet,
) -> Result<(), PrecompileError> {
if self.enabled(feature_set) {
(self.verify_fn)(data, slices, feature_set)
} else {
Err(PrecompileError::InvalidPublicKey)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::BaseSvmTypes;
#[derive(Debug)]
struct StubPrecompile {
program_id: Address,
}
impl<T: SvmTypesHost> Precompile<T> for StubPrecompile {
fn program_id(&self) -> Address {
self.program_id
}
fn verify(
&self,
_data: &[u8],
_slices: &[&[u8]],
_feature_set: &FeatureSet,
) -> Result<(), PrecompileError> {
Ok(())
}
}
fn stub() -> Arc<dyn Precompile<BaseSvmTypes>> {
Arc::new(StubPrecompile { program_id: Address::new_unique() })
}
#[test]
fn new_set_is_empty() {
let set = PrecompileSet::<BaseSvmTypes>::new();
assert!(set.is_empty());
assert_eq!(set.len(), 0);
assert!(set.program_ids().is_empty());
}
#[test]
fn insert_get_contains_and_ids() {
let first = stub();
let second = stub();
let mut set = PrecompileSet::<BaseSvmTypes>::new();
assert!(set.insert(Arc::clone(&first)).is_none());
assert!(set.insert(Arc::clone(&second)).is_none());
assert_eq!(set.len(), 2);
assert!(!set.is_empty());
assert!(set.contains(&first.program_id()));
assert!(set.contains(&second.program_id()));
assert!(set.get(&first.program_id()).is_some());
assert!(set.get(&Address::new_unique()).is_none());
let mut ids = set.program_ids();
ids.sort();
let mut want = [first.program_id(), second.program_id()];
want.sort();
assert_eq!(ids, want);
}
#[test]
fn insert_under_same_id_shadows_previous_entry() {
let program_id = Address::new_unique();
let first: Arc<dyn Precompile<BaseSvmTypes>> = Arc::new(StubPrecompile { program_id });
let second: Arc<dyn Precompile<BaseSvmTypes>> = Arc::new(StubPrecompile { program_id });
let mut set = PrecompileSet::<BaseSvmTypes>::new();
assert!(set.insert(Arc::clone(&first)).is_none());
let shadowed = set.insert(Arc::clone(&second));
assert!(shadowed.is_some());
assert!(Arc::ptr_eq(&shadowed.unwrap(), &first));
assert_eq!(set.len(), 1);
assert!(Arc::ptr_eq(&set.get(&program_id).unwrap(), &second));
}
#[test]
fn clone_shares_handlers() {
let entry = stub();
let mut set = PrecompileSet::<BaseSvmTypes>::new();
set.insert(Arc::clone(&entry));
let cloned = set.clone();
assert!(Arc::ptr_eq(&set.get(&entry.program_id()).unwrap(), &entry));
assert!(Arc::ptr_eq(&cloned.get(&entry.program_id()).unwrap(), &entry));
}
#[test]
fn no_precompiles_enables_nothing() {
let provider = NoPrecompiles;
let typed: &dyn PrecompileProvider<BaseSvmTypes> = &provider;
assert!(typed.get(&Address::new_unique()).is_none());
assert!(typed.program_ids().is_empty());
}
#[test]
fn set_is_its_own_provider() {
let entry = stub();
let mut set = PrecompileSet::<BaseSvmTypes>::new();
set.insert(Arc::clone(&entry));
let provider: &dyn PrecompileProvider<BaseSvmTypes> = &set;
assert!(provider.get(&entry.program_id()).is_some());
assert_eq!(provider.program_ids(), [entry.program_id()]);
}
#[cfg(feature = "precompiles")]
#[test]
fn native_set_resolves_every_table_id() {
let native = NativePrecompiles::<BaseSvmTypes>::new();
for precompile in agave_precompiles::get_precompiles() {
let handler = native.get(&precompile.program_id);
assert!(handler.is_some(), "missing handler for {}", precompile.program_id);
assert!(Arc::ptr_eq(
&handler.unwrap(),
&PrecompileProvider::<BaseSvmTypes>::get(&native, &precompile.program_id).unwrap()
));
}
assert!(!native.program_ids().is_empty());
}
}